/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <gtest/gtest.h>
#include <array>
#include "velox/common/base/tests/GTestUtils.h"
#include "velox/common/testutil/OptionalEmpty.h"
#include "velox/functions/prestosql/tests/utils/FunctionBaseTest.h"
#include "velox/functions/prestosql/types/BingTileType.h"

using facebook::velox::functions::test::FunctionBaseTest;
using namespace facebook::velox;

class GeometryFunctionsTest : public FunctionBaseTest {
 public:
  // A set of geometries such that:
  // 0, 1: Within (1, 0: Contains)
  // 0, 2: Touches
  // 1, 2: Overlaps
  // 0, 3: Touches
  // 1, 3: Crosses
  // 1, 4: Touches
  // 1, 5: Touches
  // 2, 3: Contains
  // 2, 4: Crosses
  // 2, 5: Crosses
  // 3, 4: Crosses
  // 3, 5: Touches
  // 4, 5: Contains
  // 1, 6: Contains
  // 2, 6: Contains
  // 1, 7: Touches
  // 2, 7: Contains
  // 3, 6: Contains
  // 3, 7: Contains
  // 4, 7: Contains
  // 5, 7: Touches
  static constexpr std::array<std::string_view, 8> kRelationGeometriesWKT = {
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))", // 0
      "POLYGON ((0 0, 0 2, 2 2, 2 0, 0 0))", // 1
      "POLYGON ((1 0, 1 1, 3 1, 3 0, 1 0))", // 2
      "LINESTRING (1 0.5, 2.5 0.5)", // 3
      "LINESTRING (2 0, 2 2)", // 4
      "LINESTRING (2 0.5, 2 2)", // 5
      "POINT (1.5 0.5)", // 6
      "POINT (2 0.5)" // 7
  };

  // Complex input polygon
  static constexpr const char* largeWkt =
      "MULTIPOLYGON (((-106.8668100 39.4333730, -106.8639910 39.4335190, -106.8622250 39.4336220, -106.8568070 39.4341570, -106.8567080 39.4341240, -106.8563840 39.4340160, -106.8559870 39.4342060, -106.8556890 39.4342510, -106.8553310 39.4342150, -106.8548870 39.4343270, -106.8545630 39.4344820, -106.8543240 39.4348160, -106.8541650 39.4349460, -106.8537070 39.4348980, -106.8532490 39.4348090, -106.8528780 39.4348840, -106.8526660 39.4350520, -106.8521110 39.4353510, -106.8514920 39.4357270, -106.8513320 39.4358650, -106.8510130 39.4359010, -106.8507060 39.4358990, -106.8504900 39.4359460, -106.8501740 39.4361500, -106.8498650 39.4361950, -106.8494020 39.4362500, -106.8488510 39.4364700, -106.8484010 39.4366930, -106.8478530 39.4368080, -106.8472030 39.4369370, -106.8468210 39.4370190, -106.8464930 39.4373020, -106.8463150 39.4376380, -106.8461720 39.4378800, -106.8460790 39.4381310, -106.8454250 39.4383640, -106.8452490 39.4386370, -106.8448210 39.4388440, -106.8442480 39.4390950, -106.8439600 39.4393710, -106.8437970 39.4396810, -106.8433000 39.4403780, -106.8435170 39.4408920, -106.8432390 39.4415700, -106.8429990 39.4419930, -106.8427580 39.4427570, -106.8426530 39.4431050, -106.8426130 39.4433350, -106.8425690 39.4438310, -106.8424250 39.4440780, -106.8423390 39.4442090, -106.8420210 39.4445630, -106.8421780 39.4449090, -106.8421790 39.4465380, -106.8426620 39.4482500, -106.8428010 39.4511016, -106.8429860 39.4540610, -106.8430810 39.4543610, -106.8436780 39.4551190, -106.8414510 39.4552500, -106.8292410 39.4553950, -106.8161060 39.4561950, -106.8050950 39.4561240, -106.7940650 39.4560720, -106.7870330 39.4560790, -106.7773920 39.4560940, -106.7646640 39.4564290, -106.7516710 39.4564950, -106.7486050 39.4565240, -106.7480860 39.4569030, -106.7476970 39.4575880, -106.7476300 39.4604340, -106.7472420 39.4612370, -106.7467300 39.4616880, -106.7462291 39.4618463, -106.7452780 39.4621470, -106.7450691 39.4622613, -106.7448850 39.4623620, -106.7437120 39.4636410, -106.7428740 39.4649650, -106.7427550 39.4652710, -106.7427600 39.4659540, -106.7431010 39.4666560, -106.7422856 39.4694222, -106.7421175 39.4693936, -106.7419082 39.4693894, -106.7416776 39.4694308, -106.7414168 39.4695313, -106.7411808 39.4697052, -106.7409286 39.4698708, -106.7404673 39.4700738, -106.7403064 39.4701193, -106.7401293 39.4701400, -106.7398987 39.4701796, -106.7416770 39.4712450, -106.7434880 39.4722230, -106.7442150 39.4726370, -106.7465272 39.4740356, -106.7483535 39.4751444, -106.7499620 39.4761650, -106.7506860 39.4766040, -106.7524547 39.4775919, -106.7533244 39.4780915, -106.7576840 39.4806170, -106.7625610 39.4844450, -106.7692930 39.4882140, -106.7714778 39.4882466, -106.7714501 39.4884412, -106.7713390 39.4886050, -106.7712623 39.4888801, -106.7712275 39.4893189, -106.7712355 39.4896749, -106.7712650 39.4899751, -106.7713643 39.4902794, -106.7714635 39.4904470, -106.7718337 39.4909086, -106.7719544 39.4910390, -106.7720724 39.4910887, -106.7721984 39.4911011, -106.7724935 39.4910473, -106.7727420 39.4909450, -106.7729792 39.4908918, -106.7732606 39.4908279, -106.7735556 39.4908031, -106.7739285 39.4908155, -106.7744880 39.4908880, -106.7748565 39.4909956, -106.7749665 39.4910804, -106.7750412 39.4912573, -106.7750228 39.4913992, -106.7749209 39.4917242, -106.7748833 39.4918463, -106.7748640 39.4919546, -106.7748753 39.4920429, -106.7749745 39.4921444, -106.7751740 39.4922200, -106.7747790 39.4928870, -106.7745190 39.4932210, -106.7744375 39.4933878, -106.7744347 39.4934229, -106.7744420 39.4934780, -106.7744930 39.4935371, -106.7745663 39.4935730, -106.7746238 39.4935841, -106.7748380 39.4935240, -106.7752640 39.4931430, -106.7755880 39.4928960, -106.7761480 39.4926340, -106.7766270 39.4924950, -106.7769950 39.4924670, -106.7775070 39.4925360, -106.7783440 39.4925670, -106.7791410 39.4926640, -106.7800360 39.4927710, -106.7807870 39.4929610, -106.7811500 39.4930730, -106.7817260 39.4934160, -106.7827480 39.4940380, -106.7833950 39.4943910, -106.7837790 39.4944290, -106.7840770 39.4944470, -106.7843340 39.4945090, -106.7845490 39.4946160, -106.7849540 39.4949060, -106.7855630 39.4954470, -106.7857500 39.4955540, -106.7860640 39.4956150, -106.7867610 39.4957030, -106.7875420 39.4957680, -106.7879550 39.4958610, -106.7882680 39.4959000, -106.7888050 39.4958260, -106.7890330 39.4958440, -106.7894340 39.4959920, -106.7899350 39.4961820, -106.7904920 39.4963490, -106.7908900 39.4963980, -106.7918750 39.4966580, -106.7931310 39.4969910, -106.7933480 39.4971630, -106.7935390 39.4974130, -106.7936930 39.4978950, -106.7937910 39.4984110, -106.7939310 39.4994320, -106.7939500 39.4999360, -106.7940420 39.5001980, -106.7940840 39.5007240, -106.7940660 39.5011190, -106.7941540 39.5012600, -106.7944000 39.5014430, -106.7950870 39.5016950, -106.7960470 39.5020640, -106.7963250 39.5024000, -106.7968450 39.5027970, -106.7970780 39.5030130, -106.7972560 39.5033280, -106.7973090 39.5037110, -106.7974520 39.5043120, -106.7974000 39.5044990, -106.7971940 39.5047660, -106.7971160 39.5050520, -106.7971800 39.5053150, -106.7975690 39.5061200, -106.7977950 39.5066100, -106.7979140 39.5073980, -106.7981930 39.5083150, -106.7984140 39.5086080, -106.7987200 39.5089320, -106.7989210 39.5090280, -106.7990480 39.5090040, -106.7995350 39.5085320, -106.7999580 39.5080020, -106.8001950 39.5078560, -106.8005190 39.5077520, -106.8009700 39.5076580, -106.8022020 39.5075940, -106.8026100 39.5074680, -106.8030030 39.5073190, -106.8035910 39.5070350, -106.8039150 39.5069540, -106.8045100 39.5069000, -106.8048360 39.5068950, -106.8057340 39.5066050, -106.8060320 39.5065590, -106.8063580 39.5065540, -106.8067170 39.5067010, -106.8068760 39.5068420, -106.8069230 39.5069940, -106.8068700 39.5071380, -106.8067750 39.5073260, -106.8065210 39.5079000, -106.8065560 39.5087110, -106.8065090 39.5090850, -106.8065310 39.5096020, -106.8064950 39.5101710, -106.8064830 39.5108190, -106.8067250 39.5119560, -106.8070650 39.5135740, -106.8070740 39.5144510, -106.8070240 39.5152530, -106.8071010 39.5155040, -106.8072890 39.5156330, -106.8077140 39.5156260, -106.8080960 39.5155980, -106.8084100 39.5156590, -106.8087140 39.5158850, -106.8090480 39.5161980, -106.8093080 39.5163050, -106.8096330 39.5163200, -106.8099150 39.5162610, -106.8101770 39.5159830, -106.8105670 39.5157240, -106.8109620 39.5156300, -106.8115160 39.5156440, -106.8115841 39.5156252, -106.8118960 39.5155390, -106.8124300 39.5153550, -106.8127370 39.5151860, -106.8128820 39.5149790, -106.8129910 39.5145790, -106.8129220 39.5141190, -106.8128450 39.5138900, -106.8129410 39.5137670, -106.8131950 39.5136980, -106.8136630 39.5137120, -106.8141480 39.5138030, -106.8154940 39.5142870, -106.8158920 39.5143250, -106.8164860 39.5142720, -106.8167430 39.5143330, -106.8171580 39.5144810, -106.8175690 39.5144850, -106.8181170 39.5142680, -106.8185950 39.5141290, -106.8189370 39.5141780, -106.8192620 39.5142700, -106.8195110 39.5144550, -106.8196320 39.5147160, -106.8196940 39.5149240, -106.8201620 39.5154870, -106.8203840 39.5158460, -106.8205330 39.5166330, -106.8206660 39.5168400, -106.8208690 39.5170340, -106.8211550 39.5171170, -106.8219670 39.5172250, -106.8224080 39.5173060, -106.8226680 39.5174670, -106.8229030 39.5178030, -106.8232120 39.5182150, -106.8236950 39.5188000, -106.8240320 39.5191900, -106.8243770 39.5193820, -106.8250920 39.5195800, -106.8260040 39.5197740, -106.8268260 39.5199890, -106.8274920 39.5202550, -106.8280810 39.5206630, -106.8283570 39.5208170, -106.8287480 39.5209230, -106.8289810 39.5210480, -106.8292090 39.5211950, -106.8294240 39.5214080, -106.8296760 39.5215880, -106.8299280 39.5217610, -106.8304420 39.5218230, -106.8308330 39.5218200, -106.8313330 39.5217340, -106.8319540 39.5216910, -106.8321890 39.5216800, -106.8324050 39.5217240, -106.8327060 39.5219200, -106.8328750 39.5221680, -106.8330670 39.5224290, -106.8334210 39.5227900, -106.8336930 39.5229930, -106.8340140 39.5234130, -106.8352950 39.5240950, -106.8359850 39.5242440, -106.8368050 39.5243680, -106.8376690 39.5245410, -106.8385620 39.5248120, -106.8391740 39.5249010, -106.8396280 39.5249050, -106.8399150 39.5250220, -106.8400600 39.5251620, -106.8413290 39.5259430, -106.8417750 39.5261560, -106.8425050 39.5264080, -106.8434770 39.5267110, -106.8444050 39.5269270, -106.8445800 39.5271110, -106.8446020 39.5273960, -106.8446020 39.5279340, -106.8445160 39.5284510, -106.8444710 39.5289010, -106.8444710 39.5299440, -106.8444580 39.5305370, -106.8445680 39.5309520, -106.8447290 39.5311470, -106.8453870 39.5313570, -106.8461390 39.5316910, -106.8470090 39.5322940, -106.8477450 39.5325160, -106.8480430 39.5327970, -106.8490190 39.5331090, -106.8497920 39.5332420, -106.8503750 39.5332650, -106.8509160 39.5333450, -106.8512460 39.5334930, -106.8516790 39.5337610, -106.8520460 39.5342380, -106.8523960 39.5345950, -106.8528230 39.5351810, -106.8532140 39.5354830, -106.8534460 39.5356760, -106.8536500 39.5358710, -106.8539490 39.5364370, -106.8541680 39.5366640, -106.8544560 39.5368350, -106.8549870 39.5370580, -106.8551330 39.5372310, -106.8554550 39.5375880, -106.8557340 39.5379460, -106.8562010 39.5384610, -106.8564240 39.5388140, -106.8565850 39.5395360, -106.8569020 39.5402450, -106.8571020 39.5408340, -106.8571850 39.5412940, -106.8569980 39.5417360, -106.8569280 39.5422750, -106.8570480 39.5430640, -106.8574140 39.5433320, -106.8577460 39.5453960, -106.8585240 39.5472180, -106.8591900 39.5478870, -106.8593910 39.5484630, -106.8602110 39.5488670, -106.8607890 39.5492760, -106.8613540 39.5497060, -106.8614750 39.5505280, -106.8607020 39.5502430, -106.8602280 39.5500420, -106.8598670 39.5498170, -106.8594490 39.5495710, -106.8588120 39.5490760, -106.8583940 39.5488300, -106.8579780 39.5486610, -106.8576340 39.5485450, -106.8572820 39.5486280, -106.8567630 39.5488660, -106.8556520 39.5492570, -106.8546910 39.5493710, -106.8539990 39.5495020, -106.8534650 39.5496860, -106.8532260 39.5497780, -106.8531730 39.5499220, -106.8532770 39.5501070, -106.8537500 39.5502970, -106.8544800 39.5505270, -106.8550550 39.5507930, -106.8560650 39.5514470, -106.8566900 39.5519860, -106.8578600 39.5532750, -106.8582780 39.5535430, -106.8587510 39.5537000, -106.8592920 39.5537800, -106.8602590 39.5538420, -106.8612960 39.5539130, -106.8619520 39.5540130, -106.8624960 39.5542020, -106.8629890 39.5546120, -106.8633260 39.5550020, -106.8635350 39.5553610, -106.8637150 39.5557430, -106.8637150 39.5562920, -106.8637310 39.5568850, -106.8638420 39.5573120, -106.8640890 39.5575170, -106.8645080 39.5578070, -106.8652260 39.5581250, -106.8660720 39.5584630, -106.8669310 39.5587240, -106.8675770 39.5590000, -106.8681800 39.5592650, -106.8672150 39.5592580, -106.8662220 39.5592850, -106.8655440 39.5593830, -106.8654959 39.5593947, -106.8648099 39.5595616, -106.8645290 39.5596300, -106.8641630 39.5597450, -106.8638940 39.5597710, -106.8632700 39.5598030, -106.8631730 39.5598024, -106.8627740 39.5598000, -106.8631841 39.5599718, -106.8633040 39.5600220, -106.8640060 39.5602640, -106.8646900 39.5603520, -106.8659110 39.5603660, -106.8669800 39.5605470, -106.8679520 39.5608170, -106.8682950 39.5609110, -106.8686970 39.5610700, -106.8691420 39.5612490, -106.8696310 39.5615270, -106.8723290 39.5611000, -106.8740390 39.5599100, -106.8745260 39.5595440, -106.8755970 39.5596580, -106.8760060 39.5595520, -106.8762610 39.5595480, -106.8765610 39.5596080, -106.8771050 39.5597970, -106.8775520 39.5600640, -106.8780150 39.5603970, -106.8782010 39.5604490, -106.8784280 39.5604230, -106.8786460 39.5603390, -106.8778110 39.5609830, -106.8755330 39.5637520, -106.8779520 39.5640550, -106.8793388 39.5640758, -106.8794200 39.5640770, -106.8789910 39.5648410, -106.8786450 39.5652590, -106.8781230 39.5657720, -106.8780070 39.5659580, -106.8772250 39.5667280, -106.8763490 39.5673400, -106.8753560 39.5683890, -106.8750170 39.5686000, -106.8733560 39.5706480, -106.8714800 39.5728830, -106.8712230 39.5733010, -106.8711970 39.5734610, -106.8711390 39.5735080, -106.8709420 39.5740150, -106.8705120 39.5746190, -106.8694350 39.5754880, -106.8690890 39.5758840, -106.8689770 39.5761840, -106.8688390 39.5766450, -106.8685260 39.5772460, -106.8684730 39.5775210, -106.8669850 39.5799550, -106.8664940 39.5806100, -106.8664110 39.5807220, -106.8657810 39.5812470, -106.8656862 39.5812908, -106.8656666 39.5813093, -106.8656608 39.5813273, -106.8656660 39.5814240, -106.8651880 39.5818680, -106.8644110 39.5823600, -106.8630980 39.5836690, -106.8628890 39.5839260, -106.8627690 39.5840200, -106.8621140 39.5849530, -106.8611000 39.5861670, -106.8607358 39.5864957, -106.8601150 39.5870560, -106.8596990 39.5876400, -106.8588080 39.5892250, -106.8585090 39.5896230, -106.8580900 39.5898800, -106.8571920 39.5902570, -106.8567430 39.5905850, -106.8566460 39.5908230, -106.8582950 39.5916809, -106.8606710 39.5929170, -106.8607670 39.5929670, -106.8621070 39.5931050, -106.8625600 39.5932910, -106.8629240 39.5936750, -106.8629230 39.5937400, -106.8634760 39.5946360, -106.8635840 39.5949040, -106.8636290 39.5955680, -106.8637380 39.5957910, -106.8642720 39.5962440, -106.8646270 39.5970690, -106.8648500 39.5973390, -106.8649890 39.5975630, -106.8661940 39.5986930, -106.8665500 39.5990460, -106.8663840 39.5992470, -106.8662480 39.5994690, -106.8661550 39.5996900, -106.8658090 39.6000360, -106.8656750 39.6003140, -106.8654660 39.6004710, -106.8651990 39.6005850, -106.8651532 39.6006482, -106.8650620 39.6007740, -106.8649160 39.6011500, -106.8647490 39.6012740, -106.8649700 39.6015670, -106.8653480 39.6019130, -106.8655550 39.6021960, -106.8657340 39.6025230, -106.8657700 39.6027980, -106.8657480 39.6030620, -106.8657150 39.6034030, -106.8658490 39.6036540, -106.8660870 39.6040250, -106.8664420 39.6045800, -106.8667340 39.6048610, -106.8668380 39.6050580, -106.8668890 39.6053540, -106.8669220 39.6055290, -106.8670710 39.6057800, -106.8672160 39.6059210, -106.8674060 39.6061160, -106.8676000 39.6064650, -106.8676050 39.6066520, -106.8675010 39.6069940, -106.8674350 39.6072040, -106.8675710 39.6074880, -106.8676640 39.6077730, -106.8675990 39.6080050, -106.8675480 39.6082260, -106.8676260 39.6084880, -106.8680740 39.6092960, -106.8684060 39.6100310, -106.8684820 39.6102130, -106.8687130 39.6103750, -106.8690450 39.6105670, -106.8692620 39.6107290, -106.8693520 39.6109040, -106.8692750 39.6111800, -106.8692940 39.6113890, -106.8695530 39.6115060, -106.8702790 39.6115710, -106.8709510 39.6117260, -106.8711660 39.6118100, -106.8714400 39.6119710, -106.8716450 39.6121880, -106.8718650 39.6124600, -106.8722120 39.6126630, -106.8728440 39.6129610, -106.8732920 39.6132510, -106.8735550 39.6135110, -106.8735890 39.6137090, -106.8735230 39.6138970, -106.8733240 39.6144390, -106.8732310 39.6146720, -106.8732390 39.6149580, -106.8734000 39.6151530, -106.8737340 39.6154230, -106.8745180 39.6160490, -106.8750400 39.6164370, -106.8751150 39.6166010, -106.8750920 39.6168100, -106.8752110 39.6170170, -106.8755840 39.6171560, -106.8759420 39.6172820, -106.8764260 39.6173070, -106.8766550 39.6173690, -106.8767570 39.6174660, -106.8767220 39.6177420, -106.8767530 39.6178740, -106.8770060 39.6182980, -106.8771670 39.6184720, -106.8779630 39.6190090, -106.8782530 39.6192350, -106.8783870 39.6194860, -106.8784680 39.6198480, -106.8786770 39.6202180, -106.8789690 39.6204990, -106.8792990 39.6206260, -106.8795600 39.6208200, -106.8796390 39.6211260, -106.8796670 39.6216430, -106.8798260 39.6222790, -106.8797620 39.6225440, -106.8796379 39.6226198, -106.8795950 39.6226460, -106.8793290 39.6228040, -106.8792090 39.6229014, -106.8791931 39.6229564, -106.8792025 39.6230157, -106.8792660 39.6230910, -106.8796100 39.6232070, -106.8798710 39.6234230, -106.8800750 39.6236060, -106.8801220 39.6237600, -106.8802000 39.6245510, -106.8801240 39.6249040, -106.8802130 39.6250560, -106.8804540 39.6250630, -106.8808110 39.6251220, -106.8813880 39.6254310, -106.8817810 39.6257750, -106.8823010 39.6261060, -106.8824190 39.6262580, -106.8824650 39.6264000, -106.8823700 39.6265550, -106.8818820 39.6269030, -106.8818280 39.6270020, -106.8818460 39.6271230, -106.8820320 39.6271860, -106.8824150 39.6271570, -106.8829180 39.6270240, -106.8833210 39.6268900, -106.8834758 39.6268206, -106.8836670 39.6267350, -106.8839356 39.6265974, -106.8845030 39.6263570, -106.8849940 39.6262240, -106.8854260 39.6262020, -106.8858880 39.6262460, -106.8863200 39.6264680, -106.8866370 39.6265570, -106.8876180 39.6266240, -106.8881080 39.6267790, -106.8884850 39.6268970, -106.8885490 39.6269990, -106.8912910 39.6275630, -106.8916410 39.6279080, -106.8920880 39.6281170, -106.8929040 39.6283870, -106.8943430 39.6293300, -106.8948280 39.6297340, -106.8953910 39.6301390, -106.8959820 39.6311820, -106.8962840 39.6316170, -106.8966680 39.6320410, -106.8967700 39.6322430, -106.8967990 39.6326320, -106.8967520 39.6333300, -106.8967050 39.6339130, -106.8968080 39.6345250, -106.8969760 39.6348200, -106.8974340 39.6352380, -106.8977060 39.6356770, -106.8978460 39.6359070, -106.8980890 39.6362600, -106.8981820 39.6364550, -106.8981820 39.6368870, -106.8984990 39.6371820, -106.8993220 39.6374700, -106.8996400 39.6378440, -106.8996120 39.6384420, -106.8996680 39.6387800, -106.8999670 39.6391980, -106.9007430 39.6398750, -106.9008090 39.6401270, -106.9009080 39.6401520, -106.9008930 39.6402390, -106.9009420 39.6404440, -106.9007740 39.6413020, -106.9007220 39.6417320, -106.9005530 39.6418610, -106.9003740 39.6420560, -106.9002060 39.6425420, -106.9001240 39.6429200, -106.8999310 39.6438110, -106.8998990 39.6441800, -106.8995690 39.6449140, -106.8993860 39.6456370, -106.8995980 39.6461010, -106.8996140 39.6463180, -106.8995647 39.6464978, -106.8995383 39.6465561, -106.8995080 39.6465990, -106.8992700 39.6470430, -106.8988730 39.6474760, -106.8987250 39.6473840, -106.8982530 39.6472930, -106.8973060 39.6472910, -106.8968040 39.6470400, -106.8964790 39.6469710, -106.8952070 39.6474270, -106.8943210 39.6476080, -106.8937890 39.6475850, -106.8919280 39.6471730, -106.8903910 39.6472170, -106.8897700 39.6471710, -106.8890310 39.6471010, -106.8865780 39.6474420, -106.8851880 39.6477840, -106.8844490 39.6477820, -106.8837990 39.6475080, -106.8832670 39.6473010, -106.8817600 39.6471860, -106.8796030 39.6472060, -106.8785400 39.6470740, -106.8784010 39.6471170, -106.8780779 39.6471546, -106.8769820 39.6472820, -106.8762330 39.6474890, -106.8761090 39.6476900, -106.8761880 39.6479500, -106.8763750 39.6482050, -106.8764540 39.6483490, -106.8764520 39.6484960, -106.8764210 39.6485740, -106.8763060 39.6487360, -106.8760950 39.6490990, -106.8760480 39.6491530, -106.8759790 39.6492090, -106.8758480 39.6492790, -106.8757710 39.6493310, -106.8756390 39.6494860, -106.8754630 39.6496500, -106.8754120 39.6497270, -106.8753680 39.6499430, -106.8753420 39.6500110, -106.8752140 39.6501370, -106.8750540 39.6504350, -106.8749170 39.6506460, -106.8748390 39.6508310, -106.8747720 39.6511360, -106.8746120 39.6514960, -106.8746420 39.6516430, -106.8746760 39.6516990, -106.8748100 39.6517680, -106.8746870 39.6519870, -106.8743360 39.6521740, -106.8743520 39.6522630, -106.8744140 39.6523110, -106.8746200 39.6524790, -106.8747210 39.6525870, -106.8748000 39.6526620, -106.8749560 39.6528090, -106.8751470 39.6527180, -106.8752250 39.6527860, -106.8753690 39.6529140, -106.8755650 39.6530150, -106.8757120 39.6530810, -106.8757680 39.6531000, -106.8758380 39.6531220, -106.8759070 39.6531370, -106.8759670 39.6531460, -106.8760690 39.6531530, -106.8762490 39.6531480, -106.8763920 39.6527010, -106.8763190 39.6524980, -106.8765760 39.6524050, -106.8773670 39.6523130, -106.8772750 39.6525170, -106.8778840 39.6528370, -106.8808280 39.6552300, -106.8818420 39.6562480, -106.8827950 39.6573670, -106.8836750 39.6583580, -106.8844080 39.6595340, -106.8845660 39.6598600, -106.8850430 39.6610540, -106.8853930 39.6617310, -106.8858210 39.6624120, -106.8860860 39.6629860, -106.8860410 39.6631770, -106.8859320 39.6632390, -106.8859320 39.6633620, -106.8860480 39.6640840, -106.8860730 39.6645010, -106.8861360 39.6647300, -106.8861840 39.6649270, -106.8860520 39.6653030, -106.8860440 39.6655000, -106.8861440 39.6660480, -106.8860640 39.6662580, -106.8863590 39.6671530, -106.8865200 39.6673480, -106.8867220 39.6674650, -106.8867540 39.6675860, -106.8865870 39.6677200, -106.8865350 39.6678750, -106.8868170 39.6688360, -106.8868230 39.6690890, -106.8866930 39.6695080, -106.8866880 39.6698480, -106.8867620 39.6704840, -106.8866850 39.6707820, -106.8868790 39.6716460, -106.8870900 39.6726090, -106.8874670 39.6734150, -106.8875980 39.6740610, -106.8877020 39.6747290, -106.8879760 39.6754050, -106.8880550 39.6757000, -106.8880640 39.6760190, -106.8882430 39.6763450, -106.8882520 39.6766740, -106.8881530 39.6777190, -106.8878190 39.6779160, -106.8877890 39.6779340, -106.8875360 39.6780260, -106.8873080 39.6779970, -106.8868610 39.6777740, -106.8865570 39.6775590, -106.8862010 39.6775430, -106.8858660 39.6777800, -106.8851140 39.6783630, -106.8847080 39.6790950, -106.8836410 39.6801120, -106.8831980 39.6805370, -106.8822320 39.6811020, -106.8814790 39.6816530, -106.8810300 39.6818580, -106.8805420 39.6822180, -106.8801130 39.6826530, -106.8798570 39.6831630, -106.8796610 39.6838030, -106.8795200 39.6843770, -106.8794170 39.6847850, -106.8792160 39.6852170, -106.8790480 39.6854500, -106.8789120 39.6855400, -106.8788300 39.6855550, -106.8783320 39.6856490, -106.8775230 39.6856950, -106.8770660 39.6856370, -106.8765060 39.6854380, -106.8762900 39.6853100, -106.8760140 39.6851050, -106.8757810 39.6849000, -106.8754720 39.6848520, -106.8741880 39.6846740, -106.8732280 39.6844040, -106.8724990 39.6842500, -106.8714860 39.6841340, -106.8707030 39.6840810, -106.8701740 39.6839900, -106.8694100 39.6838470, -106.8685730 39.6836640, -106.8682430 39.6835150, -106.8680400 39.6833750, -106.8678530 39.6833120, -106.8673250 39.6832330, -106.8665960 39.6830690, -106.8659250 39.6829800, -106.8654120 39.6829330, -106.8643260 39.6827410, -106.8636120 39.6826100, -106.8627860 39.6825570, -106.8623170 39.6825640, -106.8616090 39.6826630, -106.8613400 39.6826890, -106.8610530 39.6826060, -106.8606960 39.6825340, -106.8602390 39.6824650, -106.8598970 39.6824260, -106.8596070 39.6822330, -106.8592140 39.6818880, -106.8588960 39.6816730, -106.8584810 39.6816020, -106.8580850 39.6816740, -106.8576050 39.6818030, -106.8571380 39.6818540, -106.8568010 39.6820130, -106.8559900 39.6825310, -106.8556490 39.6825250, -106.8553350 39.6824860, -106.8547590 39.6821990, -106.8541580 39.6820540, -106.8537880 39.6820380, -106.8532360 39.6821350, -106.8527820 39.6821640, -106.8523540 39.6821370, -106.8521460 39.6821160, -106.8519090 39.6820240, -106.8517070 39.6818510, -106.8515990 39.6815340, -106.8513830 39.6813970, -106.8510690 39.6812930, -106.8507020 39.6812180, -106.8503940 39.6811950, -106.8502150 39.6812240, -106.8500500 39.6812580, -106.8499150 39.6812810, -106.8498480 39.6812470, -106.8498180 39.6811720, -106.8497280 39.6810740, -106.8495930 39.6810390, -106.8493750 39.6810480, -106.8490310 39.6810390, -106.8487610 39.6810100, -106.8485890 39.6809990, -106.8484610 39.6810390, -106.8482740 39.6810790, -106.8479820 39.6811020, -106.8475850 39.6810970, -106.8472400 39.6810740, -106.8469260 39.6810740, -106.8466940 39.6811020, -106.8465660 39.6811720, -106.8464610 39.6813500, -106.8463860 39.6815060, -106.8462440 39.6815580, -106.8460340 39.6815920, -106.8458400 39.6815750, -106.8456150 39.6815010, -106.8449400 39.6811030, -106.8444680 39.6808780, -106.8441840 39.6807170, -106.8439740 39.6805670, -106.8437340 39.6803190, -106.8436520 39.6801510, -106.8435770 39.6800530, -106.8434500 39.6799380, -106.8433450 39.6798060, -106.8432550 39.6796560, -106.8431950 39.6794310, -106.8431350 39.6792750, -106.8430450 39.6791660, -106.8427910 39.6790560, -106.8423790 39.6789460, -106.8421310 39.6789520, -106.8419290 39.6790040, -106.8417190 39.6790160, -106.8414190 39.6789350, -106.8411050 39.6787560, -106.8409470 39.6786640, -106.8407670 39.6786530, -106.8405730 39.6787860, -106.8404110 39.6789740, -106.8403090 39.6791930, -106.8401870 39.6794510, -106.8400540 39.6796000, -106.8397900 39.6798190, -106.8394540 39.6800540, -106.8390980 39.6803050, -106.8385790 39.6807670, -106.8382940 39.6809230, -106.8380810 39.6810330, -106.8378970 39.6811030, -106.8376940 39.6811110, -106.8375720 39.6810560, -106.8374600 39.6810090, -106.8372560 39.6808920, -106.8370630 39.6808530, -106.8367370 39.6808290, -106.8365640 39.6807820, -106.8365030 39.6806570, -106.8365140 39.6804930, -106.8365240 39.6802580, -106.8364120 39.6801090, -106.8361570 39.6798980, -106.8358620 39.6797570, -106.8355780 39.6796940, -106.8352620 39.6797250, -106.8349980 39.6798190, -106.8348350 39.6802730, -106.8347230 39.6805310, -106.8343870 39.6811810, -106.8342760 39.6815020, -106.8342350 39.6817450, -106.8343330 39.6823200, -106.8341770 39.6844870, -106.8341330 39.6849600, -106.8340150 39.6853240, -106.8340240 39.6862140, -106.8341140 39.6869260, -106.8339930 39.6872030, -106.8335030 39.6880010, -106.8332950 39.6882130, -106.8327110 39.6892660, -106.8325080 39.6896430, -106.8322350 39.6900540, -106.8320070 39.6905840, -106.8319590 39.6909260, -106.8320280 39.6919360, -106.8321530 39.6923620, -106.8323750 39.6926880, -106.8325370 39.6929060, -106.8326290 39.6931460, -106.8326370 39.6934420, -106.8327190 39.6938370, -106.8329020 39.6943170, -106.8331640 39.6951040, -106.8337590 39.6960730, -106.8341120 39.6965070, -106.8343600 39.6967670, -106.8346650 39.6970150, -106.8351270 39.6972600, -106.8357610 39.6975910, -106.8360240 39.6978620, -106.8366890 39.6987850, -106.8370720 39.6993180, -106.8372370 39.6996340, -106.8373290 39.6998960, -106.8376970 39.7003520, -106.8379200 39.7007110, -106.8380180 39.7012150, -106.8381760 39.7017840, -106.8381980 39.7020810, -106.8381630 39.7023890, -106.8380810 39.7030500, -106.8381460 39.7038840, -106.8382540 39.7042010, -106.8384600 39.7044400, -106.8387640 39.7046660, -106.8390320 39.7051120, -106.8391870 39.7055830, -106.8394830 39.7060290, -106.8398030 39.7062870, -106.8401120 39.7066890, -106.8408400 39.7078650, -106.8411190 39.7081900, -106.8415140 39.7086020, -106.8420190 39.7094290, -106.8422700 39.7097550, -106.8424350 39.7100930, -106.8424150 39.7103910, -106.8422090 39.7106800, -106.8419920 39.7110680, -106.8421270 39.7113620, -106.8424690 39.7119180, -106.8426250 39.7124100, -106.8426940 39.7128710, -106.8426900 39.7132560, -106.8424570 39.7136000, -106.8423650 39.7138980, -106.8424150 39.7141720, -106.8425820 39.7145550, -106.8426020 39.7147740, -106.8425740 39.7153130, -106.8427150 39.7158280, -106.8428550 39.7162660, -106.8427880 39.7164320, -106.8426230 39.7166430, -106.8418610 39.7173920, -106.8415700 39.7176930, -106.8414900 39.7178810, -106.8414980 39.7181780, -106.8413500 39.7190490, -106.8411890 39.7194250, -106.8408830 39.7196610, -106.8404120 39.7201300, -106.8401460 39.7203210, -106.8397390 39.7205040, -106.8394040 39.7207620, -106.8391010 39.7211290, -106.8381110 39.7218600, -106.8378180 39.7221060, -106.8377800 39.7222500, -106.8378270 39.7224250, -106.8381050 39.7227280, -106.8382570 39.7231000, -106.8387170 39.7237970, -106.8388070 39.7240040, -106.8390120 39.7242100, -106.8396740 39.7245070, -106.8397920 39.7246490, -106.8399860 39.7250080, -106.8402060 39.7252360, -106.8402260 39.7254670, -106.8400790 39.7258210, -106.8401810 39.7266320, -106.8401870 39.7266770, -106.8404520 39.7275310, -106.8406880 39.7278790, -106.8412220 39.7281460, -106.8414270 39.7283730, -106.8415620 39.7286350, -106.8417220 39.7287870, -106.8423830 39.7289960, -106.8425580 39.7291910, -106.8429640 39.7299990, -106.8431440 39.7303370, -106.8433920 39.7305750, -106.8436950 39.7307470, -106.8441830 39.7309260, -106.8450280 39.7311330, -106.8452050 39.7313720, -106.8452400 39.7316140, -106.8454780 39.7319950, -106.8457680 39.7321990, -106.8463740 39.7325200, -106.8473850 39.7330870, -106.8476880 39.7332580, -106.8479460 39.7332980, -106.8482020 39.7333270, -106.8485530 39.7336740, -106.8488000 39.7338790, -106.8493030 39.7340690, -106.8509470 39.7348900, -106.8520900 39.7355880, -106.8523530 39.7358480, -106.8525290 39.7359460, -106.8527022 39.7360417, -106.8528730 39.7361360, -106.8537390 39.7366070, -106.8552240 39.7373650, -106.8557440 39.7376430, -106.8561630 39.7378890, -106.8570060 39.7385580, -106.8575740 39.7390330, -106.8578390 39.7393810, -106.8580040 39.7396870, -106.8580820 39.7399390, -106.8582860 39.7401110, -106.8583720 39.7401430, -106.8585240 39.7402070, -106.8586740 39.7402700, -106.8589200 39.7404090, -106.8593130 39.7407550, -106.8594920 39.7410610, -106.8595550 39.7412910, -106.8597610 39.7415520, -106.8600510 39.7417230, -106.8609520 39.7419180, -106.8613400 39.7420440, -106.8615730 39.7422490, -106.8617820 39.7426420, -106.8619290 39.7428160, -106.8621180 39.7429450, -106.8628040 39.7430670, -106.8631520 39.7432810, -106.8638760 39.7437870, -106.8645470 39.7444040, -106.8647690 39.7446970, -106.8649310 39.7448930, -106.8652910 39.7450630, -106.8654550 39.7451120, -106.8662090 39.7453350, -106.8664700 39.7455070, -106.8668460 39.7457430, -106.8674660 39.7460530, -106.8677320 39.7464010, -106.8680140 39.7468370, -106.8683790 39.7471940, -106.8690020 39.7475910, -106.8696480 39.7478230, -106.8700620 39.7480560, -106.8703130 39.7482490, -106.8706360 39.7485410, -106.8709570 39.7487650, -106.8716240 39.7492580, -106.8717570 39.7494140, -106.8718620 39.7499490, -106.8719240 39.7501130, -106.8726090 39.7507270, -106.8727740 39.7510320, -106.8732860 39.7520770, -106.8735170 39.7527090, -106.8736170 39.7532340, -106.8736830 39.7536060, -106.8739370 39.7540400, -106.8742910 39.7544960, -106.8746100 39.7547100, -106.8750000 39.7549340, -106.8752610 39.7551170, -106.8755740 39.7554680, -106.8757530 39.7556130, -106.8758550 39.7557240, -106.8758980 39.7557870, -106.8760030 39.7560050, -106.8761550 39.7562060, -106.8762610 39.7562620, -106.8764250 39.7562910, -106.8766580 39.7562740, -106.8767640 39.7562720, -106.8768930 39.7563100, -106.8770420 39.7564340, -106.8773710 39.7567390, -106.8775210 39.7568900, -106.8777890 39.7570700, -106.8778730 39.7571630, -106.8779600 39.7573370, -106.8780510 39.7574890, -106.8780680 39.7575190, -106.8780820 39.7575420, -106.8781330 39.7577130, -106.8781790 39.7578920, -106.8783550 39.7581460, -106.8786050 39.7585420, -106.8786500 39.7586940, -106.8786430 39.7588740, -106.8785680 39.7591010, -106.8783490 39.7596450, -106.8781290 39.7601340, -106.8780690 39.7605230, -106.8780270 39.7611040, -106.8780800 39.7613550, -106.8782340 39.7618350, -106.8784960 39.7624520, -106.8788040 39.7630410, -106.8789570 39.7633310, -106.8790200 39.7637040, -106.8790570 39.7637930, -106.8792190 39.7639940, -106.8793470 39.7641530, -106.8795350 39.7644060, -106.8796770 39.7647010, -106.8798150 39.7650410, -106.8799610 39.7652370, -106.8800870 39.7653740, -106.8803420 39.7655410, -106.8805520 39.7657270, -106.8806970 39.7659140, -106.8808420 39.7661090, -106.8810160 39.7662550, -106.8813890 39.7664470, -106.8818490 39.7666420, -106.8822690 39.7668510, -106.8824220 39.7668890, -106.8829540 39.7669300, -106.8836050 39.7670190, -106.8838280 39.7670640, -106.8840410 39.7671780, -106.8842920 39.7673900, -106.8844610 39.7675850, -106.8845830 39.7677900, -106.8846240 39.7679920, -106.8846990 39.7684470, -106.8847450 39.7685800, -106.8848660 39.7687310, -106.8851230 39.7689520, -106.8855470 39.7693060, -106.8858380 39.7694990, -106.8860130 39.7696900, -106.8863930 39.7701650, -106.8866080 39.7705580, -106.8867290 39.7707090, -106.8869450 39.7708950, -106.8873250 39.7711320, -106.8875150 39.7712550, -106.8877170 39.7714050, -106.8878830 39.7714870, -106.8881680 39.7716490, -106.8885060 39.7718650, -106.8888020 39.7719810, -106.8892020 39.7720960, -106.8897680 39.7723080, -106.8898570 39.7723780, -106.8900520 39.7726810, -106.8902250 39.7728090, -106.8903390 39.7729020, -106.8904010 39.7730540, -106.8904300 39.7732510, -106.8904470 39.7734450, -106.8907000 39.7739500, -106.8908540 39.7744650, -106.8908460 39.7746000, -106.8907750 39.7747680, -106.8905800 39.7751090, -106.8905678 39.7751860, -106.8905150 39.7755200, -106.8905360 39.7756280, -106.8905900 39.7756990, -106.8907760 39.7758630, -106.8909440 39.7760260, -106.8912480 39.7762560, -106.8918830 39.7768580, -106.8922090 39.7772490, -106.8923480 39.7774360, -106.8925040 39.7775770, -106.8926760 39.7776870, -106.8928480 39.7777740, -106.8930840 39.7778650, -106.8932310 39.7779070, -106.8936830 39.7782150, -106.8942430 39.7785940, -106.8945420 39.7788630, -106.8947460 39.7790720, -106.8948630 39.7792860, -106.8949880 39.7795770, -106.8950370 39.7798780, -106.8950310 39.7800940, -106.8949460 39.7804200, -106.8948480 39.7806560, -106.8947410 39.7808290, -106.8945420 39.7810030, -106.8942960 39.7811920, -106.8941960 39.7813600, -106.8940800 39.7816140, -106.8939950 39.7819040, -106.8939660 39.7821390, -106.8938880 39.7822610, -106.8937920 39.7824070, -106.8936280 39.7826040, -106.8935630 39.7827710, -106.8935290 39.7830110, -106.8935210 39.7833800, -106.8935380 39.7839790, -106.8936260 39.7844330, -106.8936620 39.7848780, -106.8937420 39.7854670, -106.8938200 39.7857630, -106.8940160 39.7862920, -106.8942000 39.7868560, -106.8942810 39.7872510, -106.8944290 39.7879520, -106.8945100 39.7883650, -106.8946680 39.7889930, -106.8947030 39.7892270, -106.8947440 39.7894380, -106.8948380 39.7896660, -106.8950010 39.7900690, -106.8950410 39.7902530, -106.8951490 39.7905890, -106.8951990 39.7907060, -106.8953320 39.7908700, -106.8956170 39.7912390, -106.8957230 39.7914720, -106.8957680 39.7916430, -106.8958960 39.7918390, -106.8959590 39.7919910, -106.8959850 39.7921120, -106.8959690 39.7925850, -106.8958330 39.7931380, -106.8957600 39.7934630, -106.8956550 39.7936950, -106.8954360 39.7940000, -106.8952310 39.7941840, -106.8950470 39.7943040, -106.8946720 39.7944540, -106.8941570 39.7946250, -106.8935540 39.7947880, -106.8929750 39.7949600, -106.8922930 39.7952230, -106.8920020 39.7952910, -106.8903950 39.7958590, -106.8902330 39.7959150, -106.8899990 39.7959100, -106.8897460 39.7958560, -106.8893030 39.7956780, -106.8888530 39.7954510, -106.8886930 39.7953530, -106.8878000 39.7947930, -106.8877780 39.7947870, -106.8877490 39.7947890, -106.8877340 39.7948110, -106.8879080 39.7950430, -106.8881290 39.7954360, -106.8883880 39.7959280, -106.8884570 39.7961250, -106.8885360 39.7964520, -106.8885740 39.7967490, -106.8886150 39.7969690, -106.8886310 39.7975690, -106.8886020 39.7977720, -106.8884970 39.7980210, -106.8881610 39.7987660, -106.8876440 39.7999330, -106.8870750 39.8012990, -106.8869190 39.8015990, -106.8868420 39.8017760, -106.8867780 39.8019840, -106.8867590 39.8021420, -106.8864490 39.8027830, -106.8862960 39.8031820, -106.8858690 39.8039960, -106.8855240 39.8048220, -106.8851820 39.8055710, -106.8850280 39.8059440, -106.8848400 39.8062980, -106.8845260 39.8070020, -106.8842450 39.8076150, -106.8841390 39.8078200, -106.8839990 39.8080430, -106.8836820 39.8083960, -106.8835340 39.8085510, -106.8833690 39.8086890, -106.8827720 39.8090690, -106.8820410 39.8094910, -106.8802690 39.8104670, -106.8790650 39.8110770, -106.8778160 39.8117470, -106.8772630 39.8120490, -106.8769470 39.8122530, -106.8766430 39.8124600, -106.8765170 39.8125700, -106.8764400 39.8127110, -106.8763740 39.8128830, -106.8761950 39.8131830, -106.8760030 39.8134070, -106.8756150 39.8137050, -106.8752030 39.8142490, -106.8746650 39.8149170, -106.8741870 39.8156610, -106.8738590 39.8161490, -106.8732140 39.8170820, -106.8721710 39.8185380, -106.8715130 39.8194820, -106.8708240 39.8203280, -106.8703980 39.8208950, -106.8699220 39.8212210, -106.8695610 39.8215560, -106.8684950 39.8226940, -106.8677500 39.8235960, -106.8672110 39.8242190, -106.8664620 39.8249560, -106.8657030 39.8258470, -106.8649590 39.8267930, -106.8635720 39.8287050, -106.8628680 39.8295520, -106.8624190 39.8298110, -106.8619270 39.8300500, -106.8611560 39.8305020, -106.8603850 39.8309860, -106.8595640 39.8316920, -106.8587460 39.8325400, -106.8574300 39.8339450, -106.8565890 39.8346220, -106.8565380 39.8346630, -106.8551991 39.8356631, -106.8551550 39.8356960, -106.8544140 39.8362240, -106.8537400 39.8366080, -106.8526170 39.8372090, -106.8522790 39.8373680, -106.8518430 39.8375840, -106.8508590 39.8380720, -106.8505220 39.8382530, -106.8500200 39.8386890, -106.8492280 39.8394050, -106.8486720 39.8399640, -106.8484680 39.8403190, -106.8484240 39.8407810, -106.8484320 39.8410780, -106.8486310 39.8415920, -106.8486800 39.8418220, -106.8485590 39.8421320, -106.8483890 39.8426950, -106.8482340 39.8432910, -106.8480730 39.8441840, -106.8480460 39.8447570, -106.8479130 39.8456710, -106.8475770 39.8469410, -106.8474470 39.8474160, -106.8472200 39.8479690, -106.8470560 39.8482470, -106.8467540 39.8486590, -106.8461690 39.8491740, -106.8457240 39.8495980, -106.8461900 39.8499870, -106.8462670 39.8501730, -106.8462730 39.8504040, -106.8461120 39.8507800, -106.8455900 39.8515250, -106.8447850 39.8523410, -106.8441750 39.8529550, -106.8434490 39.8535270, -106.8420220 39.8545070, -106.8415860 39.8547330, -106.8414410 39.8548670, -106.8412100 39.8550800, -106.8408530 39.8555810, -106.8406250 39.8561230, -106.8405300 39.8564220, -106.8404670 39.8566210, -106.8403120 39.8572060, -106.8400230 39.8576070, -106.8397050 39.8579640, -106.8391860 39.8582990, -106.8386520 39.8585740, -106.8380300 39.8587490, -106.8364540 39.8589710, -106.8357590 39.8591030, -106.8352090 39.8593100, -106.8349347 39.8595567, -106.8348610 39.8596230, -106.8343150 39.8605340, -106.8340180 39.8611880, -106.8340290 39.8615840, -106.8342510 39.8624270, -106.8343760 39.8628430, -106.8344250 39.8630960, -106.8344300 39.8632610, -106.8344360 39.8634910, -106.8343040 39.8640420, -106.8342820 39.8641320, -106.8341520 39.8645850, -106.8341630 39.8650250, -106.8342330 39.8655300, -106.8343680 39.8657700, -106.8346930 39.8661940, -106.8351820 39.8666730, -106.8357310 39.8671790, -106.8361520 39.8674590, -106.8366440 39.8677480, -106.8371390 39.8681140, -106.8375460 39.8684160, -106.8379280 39.8688610, -106.8380800 39.8692110, -106.8382380 39.8697700, -106.8383820 39.8703840, -106.8384790 39.8709510, -106.8384920 39.8712840, -106.8384320 39.8717030, -106.8382810 39.8724760, -106.8382790 39.8729250, -106.8382820 39.8732490, -106.8382250 39.8735880, -106.8380700 39.8740760, -106.8380370 39.8745940, -106.8380720 39.8750200, -106.8382220 39.8773630, -106.8382830 39.8770240, -106.8383080 39.8768620, -106.8383880 39.8765860, -106.8385100 39.8763410, -106.8386720 39.8760240, -106.8387080 39.8758430, -106.8387350 39.8755500, -106.8387920 39.8752970, -106.8389250 39.8750030, -106.8390030 39.8748530, -106.8393170 39.8744300, -106.8397960 39.8738950, -106.8400200 39.8735270, -106.8402700 39.8732700, -106.8407290 39.8729350, -106.8411500 39.8725960, -106.8417130 39.8723450, -106.8420940 39.8722310, -106.8421380 39.8722180, -106.8425140 39.8722520, -106.8428460 39.8723450, -106.8436290 39.8725570, -106.8450050 39.8728330, -106.8456660 39.8730210, -106.8463010 39.8731710, -106.8464240 39.8731960, -106.8470750 39.8732840, -106.8477960 39.8733400, -106.8486970 39.8733760, -106.8496450 39.8734190, -106.8502530 39.8734550, -106.8508540 39.8735230, -106.8533430 39.8738470, -106.8581480 39.8744210, -106.8586650 39.8744920, -106.8593360 39.8745900, -106.8609260 39.8748730, -106.8636050 39.8753080, -106.8640210 39.8753550, -106.8644080 39.8753760, -106.8683010 39.8753550, -106.8693870 39.8753010, -106.8706940 39.8752000, -106.8713000 39.8751270, -106.8722460 39.8749050, -106.8725403 39.8748758, -106.8742580 39.8747060, -106.8744082 39.8746898, -106.8756720 39.8745540, -106.8760770 39.8745020, -106.8765310 39.8744410, -106.8769980 39.8744110, -106.8772380 39.8744070, -106.8776470 39.8744140, -106.8779760 39.8744540, -106.8782250 39.8744430, -106.8783140 39.8744390, -106.8785760 39.8743860, -106.8795320 39.8740940, -106.8801060 39.8739130, -106.8803850 39.8738460, -106.8806400 39.8737880, -106.8810790 39.8737940, -106.8813760 39.8737530, -106.8816430 39.8736860, -106.8819100 39.8736270, -106.8823010 39.8735980, -106.8827990 39.8736170, -106.8831850 39.8736380, -106.8836180 39.8736580, -106.8848190 39.8735160, -106.8850750 39.8734720, -106.8856460 39.8734260, -106.8860670 39.8734110, -106.8863000 39.8733890, -106.8866150 39.8733430, -106.8868830 39.8733210, -106.8871880 39.8733520, -106.8874570 39.8734430, -106.8877300 39.8734910, -106.8878230 39.8735080, -106.8881460 39.8735250, -106.8884490 39.8735150, -106.8887240 39.8735290, -106.8890660 39.8736220, -106.8894800 39.8738090, -106.8897040 39.8738730, -106.8900450 39.8739300, -106.8906020 39.8740020, -106.8908900 39.8740510, -106.8910850 39.8741340, -106.8911400 39.8742140, -106.8911560 39.8743670, -106.8911820 39.8744650, -106.8913320 39.8746070, -106.8914530 39.8747530, -106.8915390 39.8748960, -106.8916330 39.8751150, -106.8916650 39.8752450, -106.8916940 39.8754420, -106.8916990 39.8756310, -106.8916750 39.8757940, -106.8916000 39.8760510, -106.8915970 39.8761730, -106.8916290 39.8762400, -106.8916770 39.8763180, -106.8917790 39.8763990, -106.8918680 39.8764610, -106.8918820 39.8769600, -106.8919100 39.8771130, -106.8920100 39.8773630, -106.8920980 39.8777090, -106.8921190 39.8779200, -106.8922010 39.8781340, -106.8923120 39.8783400, -106.8924130 39.8786170, -106.8924750 39.8789580, -106.8925210 39.8791370, -106.8926150 39.8793880, -106.8926980 39.8798370, -106.8927870 39.8801140, -106.8928290 39.8803570, -106.8927860 39.8804920, -106.8927910 39.8806540, -106.8927890 39.8807940, -106.8928510 39.8809320, -106.8929660 39.8810700, -106.8931570 39.8812020, -106.8933840 39.8813870, -106.8934520 39.8815080, -106.8934980 39.8816870, -106.8935380 39.8818930, -106.8937250 39.8823130, -106.8938640 39.8824820, -106.8940460 39.8826950, -106.8942100 39.8829270, -106.8943560 39.8831220, -106.8945770 39.8833210, -106.8947980 39.8834710, -106.8950470 39.8835930, -106.8952480 39.8836700, -106.8955180 39.8837320, -106.8957600 39.8837880, -106.8957000 39.8839600, -106.8956350 39.8841370, -106.8955460 39.8843090, -106.8954550 39.8843920, -106.8953730 39.8843930, -106.8952670 39.8843680, -106.8951600 39.8842880, -106.8950400 39.8841820, -106.8948850 39.8840950, -106.8947080 39.8840350, -106.8944850 39.8840110, -106.8942270 39.8839620, -106.8940500 39.8839020, -106.8939660 39.8838440, -106.8937330 39.8836680, -106.8934820 39.8834560, -106.8932730 39.8832840, -106.8930570 39.8831170, -106.8928320 39.8830120, -106.8926730 39.8829740, -106.8925270 39.8829680, -106.8923520 39.8829980, -106.8922610 39.8830620, -106.8922440 39.8831170, -106.8923160 39.8831690, -106.8925700 39.8832460, -106.8926650 39.8833030, -106.8927250 39.8833650, -106.8928470 39.8835430, -106.8928980 39.8836960, -106.8929080 39.8838390, -106.8929920 39.8841310, -106.8931290 39.8844570, -106.8932040 39.8846180, -106.8932070 39.8847300, -106.8931980 39.8848340, -106.8931540 39.8849290, -106.8930630 39.8850210, -106.8928100 39.8851960, -106.8926210 39.8853300, -106.8925010 39.8854400, -106.8924680 39.8855170, -106.8924840 39.8856420, -106.8925690 39.8857540, -106.8927780 39.8859120, -106.8928550 39.8859740, -106.8928690 39.8860550, -106.8928190 39.8861460, -106.8927510 39.8862280, -106.8925910 39.8863740, -106.8924430 39.8865120, -106.8922890 39.8866410, -106.8921090 39.8869050, -106.8918130 39.8874050, -106.8915560 39.8878500, -106.8913710 39.8881410, -106.8912110 39.8884680, -106.8911190 39.8887670, -106.8910320 39.8890020, -106.8909260 39.8891890, -106.8907630 39.8894250, -106.8905380 39.8897530, -106.8903220 39.8899690, -106.8900930 39.8901390, -106.8896120 39.8904980, -106.8894440 39.8905730, -106.8892070 39.8906850, -106.8890340 39.8907690, -106.8887870 39.8909260, -106.8886150 39.8910550, -106.8882400 39.8914570, -106.8881780 39.8915260, -106.8881320 39.8915580, -106.8879030 39.8917190, -106.8877963 39.8917914, -106.8876040 39.8919220, -106.8874420 39.8919970, -106.8872510 39.8920680, -106.8870010 39.8921170, -106.8868210 39.8921420, -106.8865640 39.8921560, -106.8864120 39.8921720, -106.8862650 39.8921330, -106.8861450 39.8920500, -106.8859920 39.8920030, -106.8858460 39.8920010, -106.8856650 39.8920220, -106.8854580 39.8921470, -106.8852240 39.8923530, -106.8851280 39.8924810, -106.8850400 39.8926850, -106.8848120 39.8933370, -106.8845080 39.8941980, -106.8844460 39.8944780, -106.8844296 39.8945459, -106.8843730 39.8947810, -106.8842810 39.8950660, -106.8841470 39.8952840, -106.8837270 39.8957730, -106.8831650 39.8963990, -106.8825140 39.8971660, -106.8819410 39.8978150, -106.8815230 39.8983540, -106.8812940 39.8985560, -106.8811450 39.8986620, -106.8808920 39.8987960, -106.8805960 39.8989000, -106.8803580 39.8989670, -106.8799320 39.8990540, -106.8787300 39.8992980, -106.8783870 39.8993620, -106.8780090 39.8994400, -106.8773700 39.8995850, -106.8771090 39.8996700, -106.8766980 39.8998160, -106.8763900 39.8999070, -106.8759770 39.9000040, -106.8756010 39.9001400, -106.8751960 39.9002910, -106.8750150 39.9003390, -106.8746900 39.9004160, -106.8739920 39.9005670, -106.8730120 39.9008520, -106.8728150 39.9009100, -106.8726410 39.9009750, -106.8725200 39.9010450, -106.8723410 39.9011470, -106.8721700 39.9013070, -106.8720000 39.9015080, -106.8719570 39.9016260, -106.8719030 39.9018070, -106.8718840 39.9019690, -106.8718950 39.9021490, -106.8718510 39.9022630, -106.8717480 39.9023360, -106.8715970 39.9023790, -106.8714450 39.9023590, -106.8712210 39.9023090, -106.8709490 39.9022140, -106.8707480 39.9021270, -106.8705930 39.9020210, -106.8704730 39.9019020, -106.8704460 39.9017720, -106.8703900 39.9016460, -106.8702280 39.9014910, -106.8700670 39.9013860, -106.8697820 39.9012280, -106.8697040 39.9028350, -106.8697280 39.9029920, -106.8697504 39.9031408, -106.8698230 39.9036230, -106.8701660 39.9044010, -106.8701700 39.9045600, -106.8701740 39.9050200, -106.8693020 39.9062600, -106.8690110 39.9064920, -106.8684690 39.9069240, -106.8680840 39.9074960, -106.8674300 39.9079080, -106.8670450 39.9082520, -106.8668872 39.9084842, -106.8663920 39.9092130, -106.8657390 39.9099220, -106.8651258 39.9107839, -106.8649080 39.9110900, -106.8633340 39.9118000, -106.8616110 39.9124640, -106.8602750 39.9132650, -106.8599950 39.9135250, -106.8594140 39.9140660, -106.8593840 39.9142490, -106.8592360 39.9143180, -106.8590580 39.9145930, -106.8588210 39.9159420, -106.8584350 39.9164920, -106.8582280 39.9170410, -106.8583470 39.9178190, -106.8581100 39.9182080, -106.8576650 39.9191460, -106.8580510 39.9192140, -106.8755990 39.9188160, -106.8805780 39.9188220, -106.8859240 39.9188280, -106.8931390 39.9188370, -106.8984830 39.9188440, -106.9011190 39.9188470, -106.9094660 39.9188410, -106.9149210 39.9189110, -106.9256170 39.9188280, -106.9361690 39.9187430, -106.9462610 39.9187790, -106.9563540 39.9188160, -106.9650480 39.9187850, -106.9837370 39.9188710, -106.9918185 39.9188110, -106.9935710 39.9187980, -107.0006030 39.9190520, -107.0139240 39.9189980, -107.0272440 39.9189450, -107.0317600 39.9189210, -107.0319650 39.9292950, -107.0321720 39.9396700, -107.0322550 39.9438800, -107.0324270 39.9525510, -107.0324420 39.9533480, -107.0326490 39.9637260, -107.0317730 39.9637280, -107.0318720 39.9737310, -107.0319050 39.9770810, -107.0319720 39.9837340, -107.0319990 39.9918600, -107.0320270 39.9999860, -107.0320490 40.0029410, -107.0349540 40.0030180, -107.0373580 40.0030830, -107.0373510 40.0061100, -107.0373410 40.0104420, -107.0373220 40.0196210, -107.0373050 40.0276610, -107.0372880 40.0357010, -107.0372710 40.0437410, -107.0372100 40.0516600, -107.0372290 40.0533850, -107.0372680 40.0553250, -107.0372890 40.0563680, -107.0373130 40.0652110, -107.0373272 40.0725170, -107.0373300 40.0739870, -107.0373470 40.0827630, -107.0373640 40.0915390, -107.0536310 40.0915350, -107.0698920 40.0915260, -107.0742480 40.0914850, -107.0750540 40.0914830, -107.1018360 40.0914630, -107.1137210 40.0912770, -107.1256080 40.0910910, -107.1399920 40.0910530, -107.1543770 40.0910150, -107.1687620 40.0909780, -107.1831470 40.0909410, -107.1841790 40.0909380, -107.1998420 40.0908970, -107.2030750 40.0908890, -107.2155060 40.0908570, -107.2187800 40.0908480, -107.2346950 40.0908530, -107.2506110 40.0908590, -107.2542060 40.0908540, -107.2669650 40.0908360, -107.2780440 40.0908200, -107.2891240 40.0908040, -107.2887810 40.0903800, -107.2882450 40.0891450, -107.2881260 40.0884580, -107.2888710 40.0874290, -107.2888420 40.0864910, -107.2896160 40.0854620, -107.2896460 40.0850270, -107.2893780 40.0843180, -107.2882170 40.0830140, -107.2889630 40.0823510, -107.2892910 40.0818250, -107.2897380 40.0806580, -107.2905420 40.0794690, -107.2914060 40.0788520, -107.2934300 40.0784170, -107.2952470 40.0776170, -107.2966170 40.0772970, -107.3006370 40.0758790, -107.3019470 40.0752390, -107.3028700 40.0745300, -107.3035250 40.0735690, -107.3040610 40.0730430, -107.3051030 40.0723800, -107.3066810 40.0721740, -107.3074360 40.0721480, -107.3093700 40.0723750, -107.3098220 40.0723030, -107.3105170 40.0720230, -107.3115410 40.0713930, -107.3130510 40.0709230, -107.3136440 40.0706450, -107.3137080 40.0706140, -107.3141030 40.0704010, -107.3147490 40.0702460, -107.3160270 40.0701110, -107.3165650 40.0699650, -107.3170270 40.0557890, -107.3161590 40.0547260, -107.3150830 40.0541990, -107.3137630 40.0536210, -107.3134860 40.0534620, -107.3131200 40.0532060, -107.3126630 40.0527980, -107.3121800 40.0524560, -107.3114670 40.0520640, -107.3101410 40.0513320, -107.3093410 40.0509200, -107.3088590 40.0506120, -107.3084940 40.0503880, -107.3082140 40.0501190, -107.3073420 40.0492580, -107.3066300 40.0485040, -107.3062020 40.0480840, -107.3058070 40.0478290, -107.3053580 40.0476400, -107.3049980 40.0475700, -107.3042970 40.0475400, -107.3037670 40.0474960, -107.3034220 40.0474480, -107.3030710 40.0472250, -107.3021950 40.0466710, -107.3012160 40.0460100, -107.3007780 40.0457330, -107.3004010 40.0455760, -107.2998230 40.0454010, -107.2995030 40.0453260, -107.2994340 40.0453100, -107.2992590 40.0452150, -107.2991540 40.0450630, -107.2989140 40.0442990, -107.2986550 40.0437990, -107.2983410 40.0434100, -107.2976170 40.0426890, -107.2969240 40.0420440, -107.2965110 40.0416680, -107.2958240 40.0411990, -107.2952830 40.0408480, -107.2947020 40.0405520, -107.2932810 40.0399440, -107.2919340 40.0394000, -107.2910770 40.0390000, -107.2906130 40.0388010, -107.2904090 40.0386620, -107.2896850 40.0383910, -107.2892350 40.0381690, -107.2888140 40.0379910, -107.2883070 40.0377710, -107.2872310 40.0374450, -107.2863870 40.0371940, -107.2859400 40.0370720, -107.2846020 40.0368130, -107.2838540 40.0366520, -107.2830320 40.0364380, -107.2814290 40.0359320, -107.2809670 40.0357880, -107.2805440 40.0355110, -107.2800270 40.0349940, -107.2797780 40.0348350, -107.2789670 40.0345110, -107.2786320 40.0343200, -107.2772960 40.0336550, -107.2755800 40.0327570, -107.2745640 40.0323050, -107.2741280 40.0320610, -107.2738040 40.0317710, -107.2734510 40.0312510, -107.2731730 40.0308400, -107.2728790 40.0305940, -107.2723850 40.0303400, -107.2714920 40.0296990, -107.2710900 40.0292140, -107.2709980 40.0290290, -107.2705900 40.0287850, -107.2698160 40.0287120, -107.2692840 40.0286130, -107.2688190 40.0283920, -107.2684840 40.0282010, -107.2682490 40.0280080, -107.2678430 40.0278190, -107.2660350 40.0271530, -107.2656280 40.0269410, -107.2654210 40.0267370, -107.2651640 40.0262920, -107.2649960 40.0259770, -107.2647620 40.0258070, -107.2643850 40.0256500, -107.2640360 40.0254590, -107.2639420 40.0254010, -107.2636570 40.0252250, -107.2633240 40.0250890, -107.2629080 40.0250430, -107.2619800 40.0250720, -107.2616490 40.0249800, -107.2612550 40.0247570, -107.2608580 40.0244140, -107.2604640 40.0241590, -107.2598980 40.0238960, -107.2592490 40.0236890, -107.2581620 40.0232390, -107.2574520 40.0229240, -107.2561450 40.0222810, -107.2550880 40.0218520, -107.2543310 40.0214400, -107.2535300 40.0209730, -107.2524330 40.0201830, -107.2520660 40.0198830, -107.2518160 40.0196800, -107.2517260 40.0195390, -107.2516030 40.0192780, -107.2514280 40.0191720, -107.2506760 40.0188790, -107.2490260 40.0182460, -107.2486640 40.0181000, -107.2485570 40.0179900, -107.2484860 40.0179170, -107.2485040 40.0175760, -107.2486820 40.0169150, -107.2486740 40.0166840, -107.2484390 40.0164800, -107.2475770 40.0159150, -107.2472010 40.0157580, -107.2466720 40.0157570, -107.2459650 40.0159680, -107.2457970 40.0160690, -107.2455530 40.0164690, -107.2454120 40.0165370, -107.2451850 40.0165640, -107.2443990 40.0165560, -107.2441160 40.0166280, -107.2432990 40.0165440, -107.2424130 40.0165500, -107.2418830 40.0164940, -107.2406310 40.0162220, -107.2402440 40.0161960, -107.2398880 40.0162030, -107.2394890 40.0162330, -107.2392050 40.0162820, -107.2386500 40.0163470, -107.2383320 40.0163120, -107.2383220 40.0163100, -107.2383150 40.0163060, -107.2381910 40.0161760, -107.2381880 40.0161700, -107.2381890 40.0161620, -107.2382240 40.0160050, -107.2383040 40.0158380, -107.2383550 40.0156400, -107.2382620 40.0154220, -107.2378860 40.0148480, -107.2377210 40.0145980, -107.2369020 40.0140330, -107.2365920 40.0137200, -107.2363820 40.0134170, -107.2363230 40.0129250, -107.2363860 40.0122100, -107.2365170 40.0114070, -107.2364640 40.0111010, -107.2362980 40.0108300, -107.2360620 40.0106040, -107.2354390 40.0103530, -107.2351050 40.0101730, -107.2348930 40.0098150, -107.2345710 40.0095680, -107.2343070 40.0093540, -107.2342010 40.0091590, -107.2342500 40.0089270, -107.2344250 40.0085950, -107.2346850 40.0082500, -107.2350730 40.0078810, -107.2352790 40.0076360, -107.2354690 40.0073360, -107.2355630 40.0071260, -107.2355820 40.0068620, -107.2354470 40.0066460, -107.2351680 40.0064310, -107.2348620 40.0062290, -107.2346110 40.0059920, -107.2341760 40.0053320, -107.2338500 40.0049870, -107.2333440 40.0046570, -107.2330260 40.0042350, -107.2329120 40.0038090, -107.2329000 40.0034370, -107.2325690 40.0029490, -107.2323040 40.0027020, -107.2323360 40.0025300, -107.2323290 40.0020000, -107.2322090 40.0017150, -107.2319510 40.0014600, -107.2316650 40.0013110, -107.2314950 40.0011610, -107.2314820 40.0011410, -107.2314800 40.0011220, -107.2315350 40.0009840, -107.2315490 40.0009640, -107.2315780 40.0009510, -107.2317540 40.0009150, -107.2317840 40.0009140, -107.2318120 40.0009210, -107.2319950 40.0009990, -107.2321740 40.0012190, -107.2323460 40.0014510, -107.2324780 40.0018110, -107.2326060 40.0021320, -107.2326180 40.0021460, -107.2326370 40.0021580, -107.2327760 40.0021980, -107.2329480 40.0021560, -107.2329750 40.0021460, -107.2329900 40.0021290, -107.2330360 40.0020220, -107.2330280 40.0018680, -107.2328450 40.0013990, -107.2327190 40.0009990, -107.2325240 39.9999450, -107.2326330 39.9997740, -107.2328510 39.9995810, -107.2331490 39.9994270, -107.2353150 39.9988390, -107.2356130 39.9987120, -107.2357680 39.9985580, -107.2358310 39.9983820, -107.2358370 39.9979040, -107.2356990 39.9974470, -107.2353210 39.9969560, -107.2353210 39.9967010, -107.2353670 39.9965340, -107.2354470 39.9963850, -107.2379050 39.9942860, -107.2381120 39.9942120, -107.2383120 39.9941550, -107.2395160 39.9944360, -107.2398250 39.9945800, -107.2399630 39.9946600, -107.2401350 39.9946990, -107.2403990 39.9946020, -107.2407190 39.9943260, -107.2409430 39.9941850, -107.2410060 39.9941590, -107.2411320 39.9941240, -107.2413010 39.9940840, -107.2415830 39.9942530, -107.2419590 39.9943430, -107.2421840 39.9941770, -107.2421870 39.9938050, -107.2424040 39.9938230, -107.2424220 39.9941180, -107.2426880 39.9951910, -107.2427870 39.9955000, -107.2427940 39.9955160, -107.2431250 39.9967570, -107.2428010 39.9976800, -107.2429470 39.9981310, -107.2435610 39.9991460, -107.2435600 39.9995320, -107.2439650 39.9999830, -107.2533860 39.9983150, -107.2624970 39.9965620, -107.2718670 39.9949060, -107.2787320 39.9882580, -107.2855960 39.9816100, -107.2922120 39.9750580, -107.3016390 39.9713150, -107.3111850 39.9676590, -107.3190560 39.9654370, -107.3190280 39.9616040, -107.3189710 39.9539290, -107.3189150 39.9462540, -107.3188600 39.9385790, -107.3187910 39.9236020, -107.3187520 39.9149700, -107.3288270 39.9152470, -107.3405240 39.9155280, -107.3522200 39.9158110, -107.3639160 39.9160940, -107.3756120 39.9163770, -107.3869160 39.9166490, -107.3982200 39.9169210, -107.4095250 39.9171930, -107.4208300 39.9174650, -107.4316640 39.9177260, -107.4316520 39.9100980, -107.4316410 39.9024700, -107.4316390 39.9016390, -107.4316320 39.8967680, -107.4316160 39.8860980, -107.4316010 39.8749810, -107.4316730 39.8648070, -107.4317460 39.8546330, -107.4313770 39.8545170, -107.4309620 39.8542430, -107.4298940 39.8539000, -107.4295670 39.8535560, -107.4279650 39.8532130, -107.4275790 39.8528930, -107.4272230 39.8523440, -107.4267180 39.8521270, -107.4259470 39.8517950, -107.4250270 39.8516120, -107.4232460 39.8515660, -107.4228310 39.8514970, -107.4220890 39.8511770, -107.4211100 39.8511310, -107.4195670 39.8508340, -107.4187950 39.8509250, -107.4171930 39.8506730, -107.4163020 39.8504220, -107.4139290 39.8495980, -107.4119400 39.8490260, -107.4106050 39.8484760, -107.4094480 39.8474240, -107.4086770 39.8467830, -107.4084700 39.8457540, -107.4081440 39.8451360, -107.4075800 39.8444270, -107.4073130 39.8439240, -107.4070760 39.8427340, -107.4068690 39.8424140, -107.4064530 39.8420940, -107.4059490 39.8418650, -107.4056520 39.8414530, -107.4055050 39.8400800, -107.4061580 39.8390280, -107.4061880 39.8380680, -107.4060690 39.8377930, -107.4058020 39.8375190, -107.4039330 39.8368320, -107.4033110 39.8363060, -107.4020950 39.8349100, -107.4010270 39.8344070, -107.4007600 39.8341320, -107.4004640 39.8331710, -107.4002270 39.8329190, -107.3992180 39.8325530, -107.3988330 39.8322100, -107.3987440 39.8319810, -107.3980910 39.8321410, -107.3971120 39.8327810, -107.3968740 39.8328730, -107.3958950 39.8328270, -107.3937290 39.8332610, -107.3935810 39.8333520, -107.3926310 39.8334660, -107.3915040 39.8338090, -107.3888930 39.8338760, -107.3884180 39.8340590, -107.3873200 39.8339900, -107.3864900 39.8339900, -107.3848570 39.8346070, -107.3834030 39.8349720, -107.3815340 39.8357720, -107.3807320 39.8363660, -107.3801380 39.8365720, -107.3788920 39.8368910, -107.3784760 39.8375770, -107.3782380 39.8383780, -107.3780010 39.8386290, -107.3775550 39.8388580, -107.3774660 39.8394300, -107.3771100 39.8395670, -107.3756120 39.8396110, -107.3750970 39.8398150, -107.3746520 39.8400890, -107.3739110 39.8402730, -107.3736740 39.8409820, -107.3732890 39.8412800, -107.3715090 39.8418980, -107.3704410 39.8423790, -107.3684250 39.8439590, -107.3671490 39.8444170, -107.3664080 39.8450580, -107.3659040 39.8452180, -107.3651910 39.8452410, -107.3644490 39.8449900, -107.3631430 39.8443730, -107.3629060 39.8443500, -107.3617780 39.8444880, -107.3605320 39.8445110, -107.3594350 39.8450840, -107.3587520 39.8452210, -107.3561700 39.8447650, -107.3548930 39.8448120, -107.3538550 39.8455450, -107.3534990 39.8454990, -107.3522820 39.8451330, -107.3509170 39.8460680, -107.3502060 39.8462260, -107.3493160 39.8459500, -107.3490210 39.8455840, -107.3486360 39.8453320, -107.3482510 39.8453090, -107.3472120 39.8460600, -107.3468270 39.8462650, -107.3461150 39.8463100, -107.3454920 39.8466510, -107.3446020 39.8467410, -107.3432990 39.8465780, -107.3416700 39.8457520, -107.3404850 39.8454530, -107.3392690 39.8453820, -107.3370480 39.8443490, -107.3371510 39.8429840, -107.3369530 39.8428710, -107.3368650 39.8428720, -107.3367780 39.8429010, -107.3362530 39.8433050, -107.3361620 39.8433230, -107.3360630 39.8433300, -107.3359210 39.8432970, -107.3357230 39.8431390, -107.3355650 39.8429620, -107.3352830 39.8427600, -107.3350590 39.8425030, -107.3349630 39.8424470, -107.3346100 39.8423640, -107.3345010 39.8422580, -107.3344440 39.8421330, -107.3343530 39.8420450, -107.3342760 39.8419880, -107.3341680 39.8419220, -107.3337220 39.8417020, -107.3335890 39.8415650, -107.3334880 39.8413460, -107.3333930 39.8412800, -107.3332870 39.8412560, -107.3330140 39.8413150, -107.3329380 39.8413030, -107.3327300 39.8411950, -107.3326211 39.8411676, -107.3320810 39.8410320, -107.3317150 39.8409090, -107.3312510 39.8406660, -107.3311210 39.8406370, -107.3309870 39.8406310, -107.3305660 39.8406260, -107.3303080 39.8406130, -107.3299240 39.8406660, -107.3294130 39.8407440, -107.3291060 39.8408400, -107.3287940 39.8409180, -107.3286330 39.8410030, -107.3283970 39.8411250, -107.3279430 39.8415030, -107.3276540 39.8417930, -107.3275590 39.8419120, -107.3272150 39.8422700, -107.3269080 39.8425150, -107.3252540 39.8437330, -107.3247680 39.8442250, -107.3244230 39.8444640, -107.3242770 39.8446260, -107.3241270 39.8447920, -107.3240520 39.8450010, -107.3240230 39.8453480, -107.3239800 39.8454570, -107.3231670 39.8464820, -107.3226560 39.8471010, -107.3218760 39.8478650, -107.3215060 39.8481560, -107.3210020 39.8484860, -107.3200510 39.8492120, -107.3192200 39.8498640, -107.3191210 39.8498930, -107.3190400 39.8499040, -107.3189690 39.8498780, -107.3188960 39.8497990, -107.3188570 39.8496830, -107.3188070 39.8495750, -107.3186780 39.8494110, -107.3186380 39.8492410, -107.3186090 39.8490880, -107.3185460 39.8489590, -107.3184490 39.8488350, -107.3184350 39.8487720, -107.3184430 39.8486590, -107.3184400 39.8485920, -107.3183840 39.8484850, -107.3183400 39.8483860, -107.3183280 39.8482070, -107.3183020 39.8481210, -107.3182100 39.8479880, -107.3181900 39.8479250, -107.3181940 39.8478670, -107.3182030 39.8477770, -107.3181530 39.8476830, -107.3180980 39.8476210, -107.3181000 39.8475080, -107.3181240 39.8473590, -107.3181060 39.8471700, -107.3180660 39.8470230, -107.3179700 39.8467680, -107.3179720 39.8466420, -107.3179650 39.8460920, -107.3179700 39.8459070, -107.3180390 39.8455140, -107.3180040 39.8453530, -107.3179290 39.8451880, -107.3178770 39.8450490, -107.3178690 39.8447970, -107.3178420 39.8446940, -107.3177180 39.8444890, -107.3176280 39.8442570, -107.3175510 39.8433530, -107.3174800 39.8429580, -107.3174720 39.8427280, -107.3172020 39.8420050, -107.3170440 39.8416560, -107.3168850 39.8414340, -107.3165350 39.8409560, -107.3162320 39.8406330, -107.3159260 39.8402110, -107.3157300 39.8399630, -107.3155470 39.8397190, -107.3154980 39.8396440, -107.3154530 39.8395500, -107.3153600 39.8393670, -107.3153220 39.8392960, -107.3152680 39.8392340, -107.3150800 39.8390390, -107.3149950 39.8389420, -107.3149510 39.8388480, -107.3149070 39.8387590, -107.3147740 39.8386220, -107.3147250 39.8385690, -107.3146600 39.8385290, -107.3145460 39.8384550, -107.3145040 39.8384110, -107.3144550 39.8383490, -107.3144170 39.8382770, -107.3143290 39.8380860, -107.3143100 39.8380360, -107.3142730 39.8379880, -107.3140670 39.8377530, -107.3139950 39.8377090, -107.3139580 39.8376430, -107.3139000 39.8369590, -107.3138050 39.8365740, -107.3137040 39.8363410, -107.3135640 39.8360020, -107.3135050 39.8358140, -107.3134780 39.8357150, -107.3134180 39.8356490, -107.3132310 39.8354860, -107.3131470 39.8354250, -107.3131100 39.8353490, -107.3129500 39.8349510, -107.3127350 39.8344690, -107.3127070 39.8343120, -107.3126450 39.8341740, -107.3125290 39.8340680, -107.3123500 39.8339450, -107.3120650 39.8338110, -107.3119620 39.8337230, -107.3119010 39.8336390, -107.3118500 39.8335140, -107.3117570 39.8331480, -107.3116860 39.8329770, -107.3114420 39.8324740, -107.3111770 39.8320640, -107.3110470 39.8318680, -107.3109550 39.8317440, -107.3108100 39.8315840, -107.3106770 39.8314560, -107.3105560 39.8313330, -107.3103000 39.8310000, -107.3101980 39.8309250, -107.3101300 39.8308320, -107.3100990 39.8307560, -107.3100320 39.8306770, -107.3099410 39.8305930, -107.3097800 39.8305020, -107.3096670 39.8304020, -107.3096050 39.8303160, -107.3095230 39.8301330, -107.3094790 39.8300570, -107.3094120 39.8299730, -107.3091540 39.8297620, -107.3087740 39.8293760, -107.3081130 39.8288500, -107.3079680 39.8287270, -107.3078840 39.8286430, -107.3078230 39.8285770, -107.3078100 39.8285370, -107.3077600 39.8284390, -107.3076920 39.8283360, -107.3075780 39.8282350, -107.3074820 39.8281560, -107.3073920 39.8280860, -107.3073130 39.8280060, -107.3072520 39.8279310, -107.3072090 39.8278730, -107.3071490 39.8278160, -107.3071060 39.8277670, -107.3070350 39.8277230, -107.3069280 39.8276720, -107.3065020 39.8273470, -107.3064540 39.8272850, -107.3063810 39.8272140, -107.3062290 39.8270410, -107.3060870 39.8269630, -107.3060440 39.8269060, -107.3057680 39.8267040, -107.3056850 39.8266650, -107.3056310 39.8266120, -107.3054080 39.8264230, -107.3053360 39.8263700, -107.3052480 39.8263360, -107.3049460 39.8262110, -107.3046480 39.8260550, -107.3045050 39.8259770, -107.3043150 39.8258900, -107.3041380 39.8258130, -107.3040130 39.8257610, -107.3033640 39.8255670, -107.3024990 39.8253900, -107.3023700 39.8253570, -107.3022510 39.8253050, -107.3019610 39.8251890, -107.3019020 39.8251640, -107.3018010 39.8251300, -107.3011890 39.8249750, -107.3010710 39.8249640, -107.3009540 39.8249480, -107.3007140 39.8249350, -107.3003350 39.8249420, -107.2998670 39.8249340, -107.2997450 39.8249580, -107.2993570 39.8250520, -107.2992460 39.8250670, -107.2991140 39.8251150, -107.2987340 39.8252800, -107.2986360 39.8253270, -107.2985370 39.8253510, -107.2984270 39.8253630, -107.2982050 39.8253800, -107.2980770 39.8253960, -107.2979800 39.8254430, -107.2977960 39.8255370, -107.2975530 39.8256180, -107.2974190 39.8256390, -107.2972920 39.8256730, -107.2967770 39.8258230, -107.2966960 39.8258560, -107.2966220 39.8259110, -107.2965720 39.8259660, -107.2964860 39.8260180, -107.2963770 39.8260740, -107.2962600 39.8260940, -107.2961210 39.8261150, -107.2956760 39.8262500, -107.2955130 39.8262750, -107.2953900 39.8262730, -107.2953140 39.8262570, -107.2952300 39.8262090, -107.2952100 39.8261840, -107.2951690 39.8261330, -107.2949330 39.8256970, -107.2948710 39.8255950, -107.2947920 39.8255150, -107.2947160 39.8253410, -107.2947010 39.8252380, -107.2946850 39.8250890, -107.2947150 39.8249490, -107.2947400 39.8248180, -107.2947490 39.8247330, -107.2947340 39.8246430, -107.2946670 39.8245630, -107.2946470 39.8244730, -107.2946370 39.8243700, -107.2945870 39.8242630, -107.2945260 39.8241740, -107.2944650 39.8240530, -107.2944260 39.8239740, -107.2943640 39.8238890, -107.2942680 39.8238010, -107.2941370 39.8237360, -107.2940070 39.8236800, -107.2938720 39.8236560, -107.2938120 39.8236340, -107.2937820 39.8235940, -107.2937800 39.8235840, -107.2937820 39.8235740, -107.2938090 39.8235350, -107.2938890 39.8234840, -107.2939460 39.8234290, -107.2939670 39.8233610, -107.2937880 39.8229060, -107.2937210 39.8228220, -107.2936100 39.8226430, -107.2935660 39.8225630, -107.2935690 39.8224730, -107.2935640 39.8223200, -107.2935220 39.8221140, -107.2935020 39.8220480, -107.2934900 39.8220070, -107.2934510 39.8218950, -107.2933780 39.8218060, -107.2932990 39.8217180, -107.2932380 39.8216330, -107.2931880 39.8215350, -107.2931440 39.8214460, -107.2930780 39.8212310, -107.2930500 39.8211020, -107.2930110 39.8209810, -107.2929610 39.8208830, -107.2929010 39.8208080, -107.2928340 39.8207460, -107.2927960 39.8206610, -107.2927760 39.8205800, -107.2927850 39.8204900, -107.2929180 39.8201000, -107.2929420 39.8199430, -107.2930220 39.8195310, -107.2930290 39.8194050, -107.2930140 39.8192970, -107.2929840 39.8189290, -107.2929510 39.8188080, -107.2929360 39.8187140, -107.2929620 39.8186050, -107.2929980 39.8184740, -107.2930120 39.8183750, -107.2929960 39.8182440, -107.2930210 39.8181040, -107.2930900 39.8173870, -107.2931320 39.8172430, -107.2931820 39.8171420, -107.2932260 39.8170610, -107.2932690 39.8169610, -107.2932950 39.8168700, -107.2933270 39.8167700, -107.2934140 39.8165930, -107.2935030 39.8164390, -107.2935480 39.8163790, -107.2935800 39.8163110, -107.2935840 39.8162340, -107.2935400 39.8161630, -107.2934180 39.8160130, -107.2933750 39.8159410, -107.2933560 39.8158790, -107.2933530 39.8157840, -107.2933680 39.8157210, -107.2934120 39.8156530, -107.2934520 39.8156020, -107.2934610 39.8155440, -107.2934530 39.8154720, -107.2934380 39.8153820, -107.2934410 39.8152740, -107.2935840 39.8148300, -107.2936200 39.8146940, -107.2936520 39.8145950, -107.2937350 39.8144810, -107.2938250 39.8144170, -107.2939120 39.8143510, -107.2939680 39.8142830, -107.2939660 39.8142020, -107.2939450 39.8141070, -107.2939320 39.8140540, -107.2939170 39.8139770, -107.2939200 39.8138870, -107.2939400 39.8137880, -107.2939660 39.8136970, -107.2939800 39.8135890, -107.2939880 39.8134670, -107.2940010 39.8133500, -107.2939870 39.8132600, -107.2939710 39.8131350, -107.2939450 39.8130540, -107.2939610 39.8128380, -107.2939960 39.8126620, -107.2939980 39.8125310, -107.2940220 39.8123880, -107.2940820 39.8122650, -107.2940970 39.8121840, -107.2941050 39.8120750, -107.2941410 39.8119340, -107.2941920 39.8118790, -107.2942590 39.8117790, -107.2942730 39.8116710, -107.2943800 39.8112010, -107.2944050 39.8111010, -107.2943920 39.8110290, -107.2943300 39.8109500, -107.2942520 39.8108700, -107.2941960 39.8107720, -107.2941750 39.8106600, -107.2941540 39.8102150, -107.2941330 39.8100940, -107.2941000 39.8099780, -107.2940730 39.8098970, -107.2940010 39.8097430, -107.2939250 39.8095740, -107.2938260 39.8093870, -107.2937850 39.8092250, -107.2937670 39.8090280, -107.2937380 39.8088570, -107.2937190 39.8087110, -107.2936540 39.8081840, -107.2936220 39.8079060, -107.2936170 39.8077700, -107.2936410 39.8076120, -107.2937130 39.8074750, -107.2938820 39.8072920, -107.2942070 39.8070600, -107.2948050 39.8065930, -107.2950450 39.8064310, -107.2952100 39.8063190, -107.2953810 39.8061890, -107.2954530 39.8060530, -107.2954950 39.8059300, -107.2955490 39.8057850, -107.2957820 39.8052390, -107.2958360 39.8051210, -107.2958560 39.8050130, -107.2958640 39.8049090, -107.2958310 39.8047830, -107.2957800 39.8046620, -107.2957070 39.8045600, -107.2956640 39.8044930, -107.2955740 39.8044410, -107.2954850 39.8044020, -107.2953850 39.8043820, -107.2952370 39.8043080, -107.2951410 39.8042290, -107.2950380 39.8041320, -107.2949250 39.8040570, -107.2946120 39.8039510, -107.2944690 39.8038720, -107.2943840 39.8037660, -107.2943510 39.8036630, -107.2943610 39.8035520, -107.2943890 39.8034050, -107.2944440 39.8032920, -107.2945340 39.8031910, -107.2946130 39.8031120, -107.2947200 39.8029930, -107.2948050 39.8029060, -107.2948730 39.8028550, -107.2949710 39.8028030, -107.2950620 39.8027480, -107.2951420 39.8026920, -107.2952210 39.8025960, -107.2952520 39.8024820, -107.2952540 39.8023610, -107.2952440 39.8022480, -107.2952940 39.8021570, -107.2953780 39.8020610, -107.2954860 39.8019590, -107.2956060 39.8018980, -107.2957400 39.8018600, -107.2958420 39.8017900, -107.2959270 39.8016980, -107.2959760 39.8015890, -107.2960260 39.8015210, -107.2960810 39.8014200, -107.2962720 39.8011910, -107.2963230 39.8011230, -107.2963720 39.8010310, -107.2964860 39.8007720, -107.2965530 39.8006810, -107.2966880 39.8005340, -107.2968980 39.8003270, -107.2969540 39.8002780, -107.2970450 39.8001980, -107.2971130 39.8001250, -107.2971570 39.8000560, -107.2973940 39.7996140, -107.2974660 39.7995140, -107.2975460 39.7994450, -107.2976310 39.7993750, -107.2976870 39.7992980, -107.2976900 39.7992210, -107.2976870 39.7991170, -107.2977070 39.7990270, -107.2977560 39.7989310, -107.2978240 39.7988580, -107.2981110 39.7983700, -107.2981820 39.7982240, -107.2982420 39.7980740, -107.2983010 39.7979200, -107.2983660 39.7978330, -107.2984980 39.7977000, -107.2985820 39.7975990, -107.2986940 39.7974430, -107.2987820 39.7972840, -107.2988250 39.7971790, -107.2988560 39.7970530, -107.2988740 39.7968810, -107.2988630 39.7967420, -107.2988870 39.7965880, -107.2989580 39.7964240, -107.2990680 39.7962420, -107.2991050 39.7961290, -107.2991590 39.7959700, -107.2992180 39.7958240, -107.2992640 39.7956300, -107.2993070 39.7955120, -107.2993990 39.7953160, -107.2995450 39.7951190, -107.2995930 39.7949880, -107.2996070 39.7948750, -107.2996130 39.7947030, -107.2995480 39.7945200, -107.2994910 39.7943900, -107.2994620 39.7942150, -107.2994230 39.7940810, -107.2993780 39.7939830, -107.2992300 39.7937290, -107.2991380 39.7936000, -107.2990940 39.7935110, -107.2990430 39.7933810, -107.2990390 39.7932460, -107.2990750 39.7931010, -107.2992010 39.7928640, -107.2992550 39.7927420, -107.2993030 39.7925870, -107.2993640 39.7924910, -107.2994870 39.7923400, -107.2996090 39.7921440, -107.3000470 39.7916260, -107.3001200 39.7915260, -107.3002340 39.7914380, -107.3010600 39.7908360, -107.3014470 39.7905270, -107.3015670 39.7904390, -107.3016530 39.7903870, -107.3017090 39.7903320, -107.3017880 39.7902360, -107.3019390 39.7900300, -107.3019780 39.7899710, -107.3020400 39.7899290, -107.3020980 39.7898920, -107.3021610 39.7898680, -107.3022480 39.7898390, -107.3023570 39.7897920, -107.3024490 39.7897450, -107.3025170 39.7896760, -107.3025720 39.7895940, -107.3026110 39.7895210, -107.3026790 39.7894700, -107.3027710 39.7894240, -107.3028400 39.7893770, -107.3029010 39.7893080, -107.3029280 39.7892220, -107.3029700 39.7891000, -107.3030890 39.7889710, -107.3032360 39.7888330, -107.3033560 39.7887590, -107.3034770 39.7886980, -107.3038990 39.7883970, -107.3040410 39.7882860, -107.3041440 39.7882250, -107.3044520 39.7880030, -107.3045420 39.7879200, -107.3045980 39.7878290, -107.3046280 39.7876890, -107.3048340 39.7873780, -107.3049270 39.7872010, -107.3050110 39.7870950, -107.3051140 39.7870120, -107.3052560 39.7869190, -107.3053360 39.7868640, -107.3053860 39.7867860, -107.3054240 39.7866770, -107.3054550 39.7865780, -107.3054980 39.7864550, -107.3056100 39.7863090, -107.3057160 39.7861580, -107.3059910 39.7858370, -107.3060410 39.7857640, -107.3061170 39.7855780, -107.3062270 39.7853820, -107.3062940 39.7853040, -107.3063320 39.7852270, -107.3063530 39.7851270, -107.3064070 39.7850050, -107.3064800 39.7849350, -107.3065540 39.7848620, -107.3066570 39.7847920, -107.3067760 39.7846820, -107.3068830 39.7845900, -107.3069210 39.7844940, -107.3069240 39.7844130, -107.3068940 39.7841930, -107.3069310 39.7840800, -107.3069980 39.7839930, -107.3071210 39.7838460, -107.3073960 39.7835030, -107.3075380 39.7833780, -107.3076580 39.7833130, -107.3077790 39.7832660, -107.3079300 39.7832310, -107.3080330 39.7831750, -107.3080830 39.7831070, -107.3080920 39.7830300, -107.3080720 39.7829540, -107.3079700 39.7826720, -107.3079820 39.7825140, -107.3080480 39.7823820, -107.3081490 39.7822630, -107.3082960 39.7821470, -107.3084280 39.7820680, -107.3085250 39.7819940, -107.3087970 39.7815830, -107.3093130 39.7809380, -107.3094210 39.7808500, -107.3095480 39.7808160, -107.3097050 39.7807910, -107.3098320 39.7807520, -107.3099760 39.7807000, -107.3101890 39.7806150, -107.3103920 39.7805570, -107.3106120 39.7805070, -107.3107390 39.7804690, -107.3108360 39.7804130, -107.3108150 39.7802870, -107.3107930 39.7801660, -107.3107320 39.7800770, -107.3106370 39.7800340, -107.3105090 39.7800360, -107.3100650 39.7800360, -107.3099690 39.7800260, -107.3098890 39.7800170, -107.3097760 39.7799650, -107.3096000 39.7799230, -107.3094420 39.7799270, -107.3092450 39.7799760, -107.3091240 39.7800090, -107.3089670 39.7800220, -107.3088380 39.7800240, -107.3082050 39.7799370, -107.3078940 39.7798890, -107.3076480 39.7798580, -107.3074840 39.7798530, -107.3073330 39.7798870, -107.3071960 39.7799710, -107.3061570 39.7805000, -107.3059970 39.7805890, -107.3058600 39.7806950, -107.3057530 39.7807970, -107.3056670 39.7808520, -107.3051880 39.7810240, -107.3051010 39.7810350, -107.3049890 39.7810100, -107.3046010 39.7809180, -107.3044640 39.7808440, -107.3043210 39.7807610, -107.3041610 39.7806840, -107.3040130 39.7806280, -107.3038320 39.7806220, -107.3036170 39.7806540, -107.3033040 39.7807140, -107.3031240 39.7807580, -107.3029210 39.7807800, -107.3026930 39.7807710, -107.3024860 39.7807120, -107.3023490 39.7806250, -107.3022770 39.7805540, -107.3022040 39.7804700, -107.3016210 39.7799720, -107.3014710 39.7798630, -107.3012690 39.7797450, -107.3007130 39.7793550, -107.3005930 39.7792670, -107.3004380 39.7791760, -107.3002650 39.7790620, -107.3001280 39.7789700, -107.3000150 39.7788960, -107.2999290 39.7787890, -107.2998960 39.7786550, -107.2998860 39.7785250, -107.2999470 39.7782800, -107.3000830 39.7779710, -107.3001930 39.7777570, -107.3002490 39.7775310, -107.2999170 39.7776300, -107.2996860 39.7776970, -107.2993960 39.7777480, -107.2992010 39.7777630, -107.2990210 39.7778160, -107.2988200 39.7778970, -107.2987040 39.7779260, -107.2985870 39.7779200, -107.2984220 39.7778730, -107.2982210 39.7778010, -107.2980380 39.7777280, -107.2978970 39.7777080, -107.2976520 39.7777310, -107.2973560 39.7777720, -107.2971760 39.7777990, -107.2970640 39.7777830, -107.2969510 39.7777130, -107.2968770 39.7775970, -107.2967860 39.7774910, -107.2966470 39.7773590, -107.2965150 39.7772490, -107.2963540 39.7771480, -107.2962520 39.7770470, -107.2961840 39.7769260, -107.2961380 39.7767700, -107.2960370 39.7765460, -107.2959870 39.7764440, -107.2958780 39.7763380, -107.2957170 39.7762190, -107.2955380 39.7761190, -107.2953550 39.7760510, -107.2946200 39.7759030, -107.2944140 39.7758490, -107.2941960 39.7757810, -107.2938890 39.7756880, -107.2937710 39.7756500, -107.2936070 39.7756170, -107.2934770 39.7755750, -107.2933120 39.7755280, -107.2931770 39.7755130, -107.2930080 39.7755120, -107.2928970 39.7755180, -107.2928110 39.7755560, -107.2927020 39.7756300, -107.2925790 39.7757720, -107.2925430 39.7759260, -107.2924990 39.7760130, -107.2924090 39.7761090, -107.2922770 39.7761790, -107.2919640 39.7762660, -107.2917270 39.7763290, -107.2915520 39.7763380, -107.2914300 39.7763580, -107.2913200 39.7763740, -107.2912080 39.7763490, -107.2911360 39.7762920, -107.2910700 39.7762340, -107.2909870 39.7761820, -107.2908750 39.7761570, -107.2906590 39.7761660, -107.2904910 39.7761960, -107.2903920 39.7762120, -107.2902650 39.7762500, -107.2901910 39.7762920, -107.2900300 39.7763850, -107.2897130 39.7765820, -107.2895930 39.7767410, -107.2894520 39.7769010, -107.2894120 39.7769380, -107.2893330 39.7770070, -107.2892240 39.7770590, -107.2891070 39.7770610, -107.2887430 39.7769920, -107.2880920 39.7769190, -107.2878870 39.7769000, -107.2876950 39.7769040, -107.2874910 39.7769260, -107.2873110 39.7769570, -107.2871260 39.7770150, -107.2866940 39.7771720, -107.2865190 39.7771840, -107.2863500 39.7772100, -107.2861770 39.7772490, -107.2860140 39.7772710, -107.2858560 39.7772740, -107.2856750 39.7772640, -107.2854990 39.7772360, -107.2853520 39.7772030, -107.2852340 39.7771640, -107.2851100 39.7771220, -107.2849320 39.7770350, -107.2848000 39.7769340, -107.2847580 39.7768860, -107.2846810 39.7768550, -107.2845640 39.7768530, -107.2844310 39.7768740, -107.2843150 39.7769120, -107.2841700 39.7769470, -107.2839900 39.7769820, -107.2838810 39.7770330, -107.2838130 39.7770890, -107.2833920 39.7775880, -107.2833060 39.7776480, -107.2832140 39.7776810, -107.2831030 39.7776790, -107.2829960 39.7776360, -107.2829000 39.7775390, -107.2828260 39.7774370, -107.2827470 39.7773440, -107.2826090 39.7772340, -107.2825190 39.7771640, -107.2824070 39.7771030, -107.2822250 39.7770700, -107.2819430 39.7770310, -107.2818020 39.7770070, -107.2816560 39.7769960, -107.2815330 39.7770030, -107.2814180 39.7770410, -107.2813140 39.7770840, -107.2811590 39.7771450, -107.2808930 39.7772270, -107.2806090 39.7773050, -107.2805050 39.7773200, -107.2804040 39.7773180, -107.2802220 39.7772780, -107.2799540 39.7772110, -107.2797210 39.7771670, -107.2791040 39.7770220, -107.2789590 39.7770110, -107.2788060 39.7770220, -107.2786750 39.7771390, -107.2786550 39.7773100, -107.2780430 39.7764300, -107.2782030 39.7762790, -107.2784070 39.7761400, -107.2782140 39.7758890, -107.2774730 39.7754310, -107.2763840 39.7750290, -107.2752500 39.7746380, -107.2751780 39.7746050, -107.2746550 39.7743030, -107.2745680 39.7739350, -107.2740880 39.7734770, -107.2737250 39.7731640, -107.2735220 39.7728180, -107.2734290 39.7727780, -107.2734220 39.7727730, -107.2734190 39.7727660, -107.2733920 39.7725930, -107.2733900 39.7725860, -107.2733850 39.7725790, -107.2733790 39.7725750, -107.2725050 39.7721050, -107.2719960 39.7721160, -107.2713570 39.7719370, -107.2707910 39.7716250, -107.2695270 39.7704750, -107.2690760 39.7704080, -107.2678130 39.7705080, -107.2661850 39.7701400, -107.2655170 39.7702290, -107.2649070 39.7706760, -107.2641520 39.7709210, -107.2630330 39.7716920, -107.2624080 39.7718700, -107.2616530 39.7719370, -107.2608690 39.7721830, -107.2597640 39.7728290, -107.2592710 39.7732650, -107.2586460 39.7743480, -107.2582100 39.7746940, -107.2577160 39.7749280, -107.2559150 39.7752970, -107.2558630 39.7753310, -107.2555680 39.7755200, -107.2553300 39.7760000, -107.2549150 39.7762290, -107.2539660 39.7764570, -107.2520980 39.7767310, -107.2506100 39.7772200, -107.2495360 39.7775050, -107.2478760 39.7775290, -107.2472530 39.7773010, -107.2468670 39.7769580, -107.2465400 39.7758600, -107.2458580 39.7753800, -107.2440190 39.7745110, -107.2436340 39.7741680, -107.2435440 39.7738940, -107.2433070 39.7736200, -107.2423880 39.7732540, -107.2417650 39.7728650, -107.2412910 39.7727650, -107.2412510 39.7727590, -107.2412120 39.7727590, -107.2411720 39.7727690, -107.2411380 39.7727850, -107.2408780 39.7729800, -107.2404440 39.7731010, -107.2404070 39.7731090, -107.2403720 39.7731100, -107.2403380 39.7731020, -107.2403110 39.7730870, -107.2400750 39.7729350, -107.2398670 39.7724090, -107.2392450 39.7719290, -107.2389780 39.7718140, -107.2386510 39.7709680, -107.2378790 39.7686810, -107.2374040 39.7677200, -107.2369880 39.7671260, -107.2363060 39.7664170, -107.2347940 39.7655940, -107.2341120 39.7654800, -107.2326590 39.7647480, -107.2318580 39.7645660, -107.2304060 39.7645890, -107.2300500 39.7644750, -107.2299310 39.7642920, -107.2299010 39.7635830, -107.2289510 39.7624620, -107.2288330 39.7621650, -107.2286540 39.7616160, -107.2282090 39.7608150, -107.2269340 39.7598090, -107.2267270 39.7595350, -107.2263410 39.7592370, -107.2245630 39.7588260, -107.2237030 39.7582550, -107.2226950 39.7577750, -107.2215090 39.7574780, -107.2195530 39.7575920, -107.2181300 39.7570440, -107.2177450 39.7570440, -107.2125300 39.7589440, -107.2119960 39.7593330, -107.2115810 39.7597450, -107.2098630 39.7606140, -107.2084400 39.7608210, -107.2079940 39.7610040, -107.2076390 39.7614380, -107.2065720 39.7618050, -107.2044080 39.7619880, -107.2041410 39.7618960, -107.2037860 39.7616450, -107.2026290 39.7600440, -107.1986570 39.7566810, -107.1978570 39.7558350, -107.1969970 39.7551030, -107.1969970 39.7550110, -107.1950120 39.7547140, -107.1940340 39.7543710, -107.1911290 39.7528380, -107.1907140 39.7524720, -107.1905840 39.7521880, -107.1902100 39.7523580, -107.1895880 39.7519230, -107.1891730 39.7514890, -107.1881650 39.7507570, -107.1869500 39.7504710, -107.1867730 39.7504020, -107.1868350 39.7499830, -107.1868070 39.7496380, -107.1865700 39.7495120, -107.1860960 39.7492900, -107.1849400 39.7493130, -107.1842880 39.7494500, -107.1833820 39.7499830, -107.1832160 39.7503220, -107.1821200 39.7504140, -107.1814090 39.7503680, -107.1810850 39.7499830, -107.1809100 39.7495700, -107.1790130 39.7485130, -107.1783610 39.7485360, -107.1768500 39.7490390, -107.1758420 39.7495130, -107.1751310 39.7495470, -107.1748940 39.7496040, -107.1747140 39.7499840, -107.1737920 39.7508940, -107.1726960 39.7513060, -107.1713320 39.7515800, -107.1702650 39.7515800, -107.1682500 39.7514190, -107.1650490 39.7518760, -107.1633010 39.7520590, -107.1616710 39.7521040, -107.1598630 39.7519210, -107.1590330 39.7517610, -107.1582920 39.7515090, -107.1574930 39.7510050, -107.1566620 39.7504810, -107.1573980 39.7501810, -107.1572880 39.7429610, -107.1572040 39.7416090, -107.1573530 39.7378730, -107.1576420 39.7322070, -107.1577010 39.7292160, -107.1579360 39.7241690, -107.1580290 39.7191110, -107.1581020 39.7164600, -107.1580430 39.7147810, -107.1580850 39.7126700, -107.1581430 39.7070980, -107.1581460 39.6998630, -107.1580910 39.6950870, -107.1580340 39.6908890, -107.1579890 39.6872690, -107.1580400 39.6839130, -107.1579490 39.6790960, -107.1579170 39.6778050, -107.1594630 39.6778140, -107.1593110 39.6755640, -107.1592810 39.6716570, -107.1592360 39.6676730, -107.1593380 39.6601660, -107.1580650 39.6605710, -107.1574080 39.6605980, -107.1568210 39.6610050, -107.1563480 39.6611880, -107.1555780 39.6613030, -107.1553770 39.6612610, -107.1551050 39.6613720, -107.1541570 39.6614630, -107.1524410 39.6612790, -107.1519380 39.6611190, -107.1511690 39.6605930, -107.1504410 39.6603820, -107.1498660 39.6604090, -107.1492940 39.6605140, -107.1448920 39.6613230, -107.1432650 39.6613910, -107.1425050 39.6613480, -107.1416160 39.6613220, -107.1397010 39.6611170, -107.1382350 39.6606660, -107.1369880 39.6604670, -107.1360980 39.6600230, -107.1351430 39.6594410, -107.1323830 39.6582050, -107.1311960 39.6580040, -107.1303500 39.6580970, -107.1289650 39.6584160, -107.1266670 39.6585500, -107.1254390 39.6589580, -107.1251690 39.6590580, -107.1238530 39.6596080, -107.1232680 39.6599340, -107.1228310 39.6600380, -107.1210990 39.6600980, -107.1196280 39.6597740, -107.1195420 39.6597450, -107.1185320 39.6594530, -107.1152440 39.6580770, -107.1142860 39.6570870, -107.1141060 39.6568110, -107.1137460 39.6565360, -107.1134830 39.6564950, -107.1134860 39.6540400, -107.1135050 39.6404660, -107.1135314 39.6378789, -107.1135493 39.6360648, -107.1135696 39.6342482, -107.1136307 39.6322583, -107.1136944 39.6303066, -107.1137631 39.6283090, -107.1138293 39.6263599, -107.1138522 39.6255063, -107.1138677 39.6250839, -107.1138714 39.6250419, -107.1138738 39.6249925, -107.1138740 39.6248000, -107.1138660 39.6246620, -107.1138680 39.6246220, -107.1138740 39.6244980, -107.1138660 39.6243480, -107.1137740 39.6131570, -107.1136830 39.6018520, -107.1135990 39.5914060, -107.1135980 39.5912690, -107.1136340 39.5825840, -107.1136710 39.5738990, -107.1137028 39.5664400, -107.1137080 39.5652140, -107.1137450 39.5565300, -107.1137920 39.5453600, -107.1138390 39.5341910, -107.1138790 39.5246920, -107.1139130 39.5166520, -107.1139470 39.5086130, -107.1097821 39.5101574, -107.1034240 39.5125150, -107.0984806 39.5143307, -107.0983988 39.5143782, -107.0975909 39.5146777, -107.0975064 39.5146952, -107.0929000 39.5164170, -107.0895520 39.5176210, -107.0892530 39.5180850, -107.0892070 39.5180720, -107.0888810 39.5180050, -107.0886460 39.5179560, -107.0884600 39.5179870, -107.0884200 39.5180260, -107.0883470 39.5180970, -107.0882180 39.5184410, -107.0881420 39.5186050, -107.0879260 39.5187710, -107.0876160 39.5189110, -107.0874370 39.5191580, -107.0865380 39.5202100, -107.0861460 39.5207120, -107.0859090 39.5209690, -107.0855450 39.5212360, -107.0846800 39.5218640, -107.0837920 39.5225110, -107.0834720 39.5227150, -107.0807800 39.5241240, -107.0796720 39.5247920, -107.0794080 39.5249050, -107.0791680 39.5249290, -107.0785400 39.5249900, -107.0783330 39.5250480, -107.0782360 39.5251260, -107.0777650 39.5256190, -107.0774580 39.5258500, -107.0767830 39.5262400, -107.0761080 39.5265950, -107.0756140 39.5268200, -107.0752060 39.5271790, -107.0749590 39.5274890, -107.0746750 39.5277200, -107.0743110 39.5279960, -107.0739100 39.5282110, -107.0736600 39.5284140, -107.0735260 39.5285780, -107.0735310 39.5287580, -107.0735840 39.5289650, -107.0737930 39.5293480, -107.0738430 39.5294730, -107.0738130 39.5296270, -107.0735940 39.5297030, -107.0734080 39.5297060, -107.0732080 39.5296470, -107.0726580 39.5295310, -107.0723210 39.5295550, -107.0718860 39.5295970, -107.0714010 39.5297600, -107.0709960 39.5298380, -107.0706080 39.5299720, -107.0699260 39.5302990, -107.0693840 39.5303780, -107.0691280 39.5303750, -107.0678170 39.5301100, -107.0667550 39.5301020, -107.0664090 39.5300720, -107.0660900 39.5299350, -107.0650700 39.5293130, -107.0644090 39.5289200, -107.0642070 39.5288700, -107.0637980 39.5288680, -107.0635190 39.5288720, -107.0631010 39.5289050, -107.0622190 39.5291370, -107.0614320 39.5293830, -107.0609340 39.5294850, -107.0594150 39.5297790, -107.0592070 39.5298430, -107.0590130 39.5299910, -107.0585341 39.5302371, -107.0552200 39.5319400, -107.0548530 39.5321360, -107.0546110 39.5322120, -107.0542500 39.5321910, -107.0536900 39.5321470, -107.0531900 39.5321560, -107.0529700 39.5321780, -107.0528210 39.5322790, -107.0526750 39.5324350, -107.0524830 39.5326460, -107.0522200 39.5327940, -107.0518710 39.5328090, -107.0515660 39.5327070, -107.0514860 39.5326240, -107.0513260 39.5324580, -107.0512530 39.5323140, -107.0511900 39.5321910, -107.0510250 39.5313200, -107.0509480 39.5310960, -107.0508520 39.5309710, -107.0506580 39.5307310, -107.0502640 39.5303870, -107.0497410 39.5299910, -107.0493260 39.5297280, -107.0488430 39.5295200, -107.0484460 39.5294640, -107.0480120 39.5294510, -107.0477320 39.5294380, -107.0475460 39.5294460, -107.0473970 39.5295290, -107.0472500 39.5296720, -107.0471550 39.5297900, -107.0470520 39.5298420, -107.0469100 39.5298600, -107.0470210 39.5296780, -107.0472230 39.5294400, -107.0473690 39.5292660, -107.0475520 39.5291370, -107.0477220 39.5289990, -107.0485220 39.5280930, -107.0486420 39.5279400, -107.0487130 39.5277810, -107.0487150 39.5276500, -107.0485980 39.5273690, -107.0484430 39.5269950, -107.0484000 39.5267350, -107.0484300 39.5261580, -107.0484940 39.5255620, -107.0485490 39.5250750, -107.0486450 39.5247760, -107.0487440 39.5245760, -107.0487940 39.5243050, -107.0488100 39.5240340, -107.0487900 39.5237730, -107.0486910 39.5235680, -107.0486400 39.5234160, -107.0486520 39.5229650, -107.0486800 39.5227190, -107.0486720 39.5224180, -107.0486450 39.5221120, -107.0485440 39.5216450, -107.0484640 39.5212950, -107.0483410 39.5208830, -107.0483360 39.5206110, -107.0483100 39.5197290, -107.0482350 39.5195320, -107.0481740 39.5194340, -107.0481590 39.5193350, -107.0482830 39.5191890, -107.0484200 39.5190960, -107.0484810 39.5188160, -107.0484710 39.5184650, -107.0483840 39.5182860, -107.0483570 39.5181340, -107.0483990 39.5179980, -107.0485200 39.5177340, -107.0485780 39.5173550, -107.0486550 39.5168050, -107.0487020 39.5164440, -107.0486230 39.5161210, -107.0483610 39.5159180, -107.0480190 39.5157440, -107.0477500 39.5157040, -107.0469130 39.5157090, -107.0466100 39.5156790, -107.0464350 39.5155940, -107.0464050 39.5155340, -107.0463340 39.5153950, -107.0462680 39.5151350, -107.0462490 39.5148830, -107.0462310 39.5146850, -107.0463070 39.5144680, -107.0467850 39.5133520, -107.0468750 39.5128460, -107.0469440 39.5124130, -107.0468900 39.5121710, -107.0467810 39.5120010, -107.0466380 39.5119050, -107.0464150 39.5118280, -107.0462370 39.5117050, -107.0461030 39.5115000, -107.0459650 39.5112750, -107.0458120 39.5110820, -107.0457250 39.5109220, -107.0454910 39.5108540, -107.0452450 39.5108130, -107.0449320 39.5108450, -107.0447010 39.5108850, -107.0446300 39.5108390, -107.0444870 39.5107450, -107.0442960 39.5105680, -107.0440840 39.5104820, -107.0439090 39.5104580, -107.0437120 39.5104890, -107.0433100 39.5106760, -107.0430260 39.5108880, -107.0428700 39.5111250, -107.0427150 39.5113880, -107.0424430 39.5116450, -107.0421730 39.5119290, -107.0419210 39.5120780, -107.0413830 39.5123750, -107.0410950 39.5124790, -107.0407480 39.5125210, -107.0404460 39.5125630, -107.0401570 39.5125950, -107.0398060 39.5125200, -107.0396620 39.5124950, -107.0392910 39.5124300, -107.0388940 39.5123650, -107.0386820 39.5122870, -107.0385270 39.5121550, -107.0383460 39.5119420, -107.0381790 39.5118010, -107.0379670 39.5117050, -107.0377320 39.5116110, -107.0375640 39.5114330, -107.0373570 39.5111220, -107.0371750 39.5108820, -107.0369490 39.5106970, -107.0365350 39.5104700, -107.0363100 39.5103210, -107.0362000 39.5101430, -107.0362010 39.5097910, -107.0362370 39.5094210, -107.0362230 39.5089450, -107.0361120 39.5083160, -107.0359900 39.5077330, -107.0358770 39.5074470, -107.0356570 39.5070820, -107.0353820 39.5068250, -107.0350970 39.5066140, -107.0348010 39.5064480, -107.0344230 39.5062480, -107.0340660 39.5059660, -107.0338670 39.5059160, -107.0336250 39.5060010, -107.0333520 39.5062130, -107.0330950 39.5065680, -107.0328750 39.5070130, -107.0326580 39.5071520, -107.0324070 39.5073010, -107.0320890 39.5075760, -107.0317620 39.5079150, -107.0315040 39.5082620, -107.0310930 39.5089260, -107.0308450 39.5091740, -107.0306280 39.5093120, -107.0304310 39.5093340, -107.0299530 39.5093060, -107.0292890 39.5092730, -107.0287540 39.5092730, -107.0284150 39.5092160, -107.0280160 39.5090700, -107.0278700 39.5089580, -107.0278640 39.5089470, -107.0277870 39.5088040, -107.0276690 39.5086460, -107.0274420 39.5085580, -107.0271890 39.5085550, -107.0269680 39.5085640, -107.0267440 39.5086350, -107.0265050 39.5087180, -107.0262140 39.5087230, -107.0258160 39.5086310, -107.0232120 39.5078750, -107.0228010 39.5077290, -107.0223520 39.5075120, -107.0219620 39.5073030, -107.0215960 39.5070930, -107.0212980 39.5068470, -107.0210210 39.5065090, -107.0208270 39.5062610, -107.0205630 39.5059590, -107.0203830 39.5057820, -107.0201570 39.5056060, -107.0195650 39.5052470, -107.0192330 39.5050370, -107.0188900 39.5048360, -107.0186190 39.5046970, -107.0183600 39.5046020, -107.0180090 39.5045180, -107.0175650 39.5044630, -107.0173320 39.5044400, -107.0171080 39.5043450, -107.0168930 39.5041420, -107.0162890 39.5033690, -107.0156690 39.5024680, -107.0155010 39.5022390, -107.0153780 39.5020310, -107.0153220 39.5017340, -107.0153820 39.5010680, -107.0153210 39.5007890, -107.0152420 39.5006470, -107.0150430 39.5005220, -107.0146220 39.5004210, -107.0138930 39.5001360, -107.0134470 39.4999020, -107.0130880 39.4997730, -107.0126500 39.4997000, -107.0124520 39.4996620, -107.0122170 39.4996170, -107.0118170 39.4994440, -107.0113700 39.4992720, -107.0106810 39.4989770, -107.0103660 39.4987210, -107.0102090 39.4985250, -107.0101800 39.4983180, -107.0103270 39.4977750, -107.0102750 39.4975770, -107.0100540 39.4973870, -107.0097600 39.4972750, -107.0087840 39.4970890, -107.0083030 39.4969310, -107.0080080 39.4967960, -107.0075950 39.4965690, -107.0071170 39.4961540, -107.0068300 39.4960600, -107.0066380 39.4960630, -107.0062390 39.4961240, -107.0055650 39.4961720, -107.0050630 39.4962850, -107.0044470 39.4962910, -107.0041090 39.4962880, -107.0036630 39.4961420, -107.0029460 39.4958750, -107.0025550 39.4958010, -107.0019530 39.4956940, -107.0016940 39.4956000, -107.0010390 39.4952640, -107.0004780 39.4949410, -107.0000320 39.4947060, -106.9997800 39.4943140, -106.9995860 39.4939660, -106.9993970 39.4937930, -106.9990640 39.4935780, -106.9985050 39.4933670, -106.9972930 39.4930910, -106.9968920 39.4929760, -106.9966040 39.4927940, -106.9957640 39.4921810, -106.9955270 39.4918330, -106.9952570 39.4913090, -106.9951530 39.4911460, -106.9949350 39.4909400, -106.9942140 39.4905340, -106.9940580 39.4905370, -106.9938630 39.4906390, -106.9935290 39.4908760, -106.9933050 39.4909560, -106.9927950 39.4909870, -106.9922160 39.4910630, -106.9916310 39.4911610, -106.9912140 39.4912220, -106.9884130 39.4910250, -106.9879190 39.4910020, -106.9874160 39.4912530, -106.9853340 39.4923330, -106.9848710 39.4925060, -106.9845640 39.4926870, -106.9844770 39.4931060, -106.9843880 39.4934930, -106.9840950 39.4941580, -106.9837430 39.4947470, -106.9831950 39.4953950, -106.9827100 39.4957990, -106.9823770 39.4960460, -106.9818150 39.4962320, -106.9814880 39.4962040, -106.9812020 39.4960990, -106.9808540 39.4958520, -106.9806180 39.4955150, -106.9803440 39.4943530, -106.9798720 39.4932050, -106.9789890 39.4915580, -106.9786980 39.4907930, -106.9786030 39.4904320, -106.9785960 39.4901790, -106.9787650 39.4896480, -106.9789680 39.4893140, -106.9793760 39.4882070, -106.9794100 39.4879100, -106.9792290 39.4875170, -106.9787730 39.4869190, -106.9782060 39.4864230, -106.9778010 39.4861320, -106.9774950 39.4858510, -106.9770990 39.4853850, -106.9764140 39.4847140, -106.9759070 39.4843160, -106.9758160 39.4842740, -106.9754900 39.4841250, -106.9750760 39.4840210, -106.9743810 39.4840000, -106.9726650 39.4839630, -106.9705240 39.4839650, -106.9699030 39.4840530, -106.9690290 39.4842430, -106.9684220 39.4843190, -106.9679360 39.4843358, -106.9677850 39.4843410, -106.9670880 39.4842650, -106.9665880 39.4841300, -106.9660570 39.4838750, -106.9653630 39.4834250, -106.9649390 39.4829590, -106.9646450 39.4825680, -106.9642840 39.4823210, -106.9637270 39.4821870, -106.9633430 39.4821600, -106.9627340 39.4821810, -106.9612200 39.4822620, -106.9594790 39.4823350, -106.9568850 39.4823560, -106.9562890 39.4823330, -106.9550980 39.4823310, -106.9549540 39.4822230, -106.9538240 39.4808670, -106.9523240 39.4789340, -106.9518530 39.4783040, -106.9511550 39.4776780, -106.9504860 39.4770510, -106.9500360 39.4766620, -106.9497190 39.4764700, -106.9484970 39.4758520, -106.9481390 39.4757490, -106.9475000 39.4756820, -106.9460070 39.4755200, -106.9454790 39.4753970, -106.9450210 39.4752290, -106.9445020 39.4749290, -106.9435480 39.4742630, -106.9433280 39.4739810, -106.9429960 39.4737780, -106.9418620 39.4732470, -106.9417150 39.4731690, -106.9414590 39.4730330, -106.9411600 39.4729940, -106.9407230 39.4730680, -106.9399350 39.4732900, -106.9396670 39.4733050, -106.9392810 39.4731910, -106.9383118 39.4727874, -106.9378330 39.4725880, -106.9375440 39.4723840, -106.9349140 39.4710970, -106.9344250 39.4708410, -106.9340350 39.4705510, -106.9336540 39.4701180, -106.9333970 39.4695280, -106.9329430 39.4684800, -106.9327070 39.4681540, -106.9322440 39.4677990, -106.9319220 39.4674420, -106.9317820 39.4670040, -106.9315300 39.4662670, -106.9314900 39.4661510, -106.9312790 39.4657150, -106.9309600 39.4654570, -106.9305890 39.4653420, -106.9301490 39.4653490, -106.9299400 39.4654520, -106.9296620 39.4656540, -106.9287880 39.4663390, -106.9285930 39.4664861, -106.9283850 39.4666430, -106.9280060 39.4667480, -106.9266010 39.4666950, -106.9254390 39.4666810, -106.9249400 39.4666010, -106.9243830 39.4664350, -106.9240070 39.4661440, -106.9226870 39.4645390, -106.9226660 39.4645140, -106.9223350 39.4641050, -106.9218700 39.4636840, -106.9214510 39.4634050, -106.9210790 39.4632790, -106.9202840 39.4632160, -106.9192740 39.4630900, -106.9186600 39.4629130, -106.9178430 39.4625530, -106.9169530 39.4621390, -106.9167158 39.4620424, -106.9164080 39.4619170, -106.9159300 39.4615510, -106.9155510 39.4611510, -106.9149470 39.4602920, -106.9144960 39.4593760, -106.9142000 39.4588980, -106.9138770 39.4584970, -106.9132950 39.4574180, -106.9129630 39.4566760, -106.9126890 39.4559660, -106.9126280 39.4553080, -106.9124920 39.4549910, -106.9122000 39.4546880, -106.9118960 39.4544520, -106.9114510 39.4542760, -106.9111230 39.4541460, -106.9108630 39.4539850, -106.9106000 39.4536820, -106.9103490 39.4533010, -106.9100760 39.4526570, -106.9097970 39.4517720, -106.9095450 39.4508530, -106.9094620 39.4504260, -106.9094430 39.4497230, -106.9096120 39.4486540, -106.9096270 39.4481590, -106.9095010 39.4477000, -106.9092780 39.4473190, -106.9089580 39.4469940, -106.9080000 39.4462500, -106.9079740 39.4462300, -106.9064750 39.4453100, -106.9053530 39.4446810, -106.9033670 39.4435490, -106.9028050 39.4432070, -106.9023600 39.4429950, -106.9019260 39.4426390, -106.9013280 39.4420340, -106.9009760 39.4415780, -106.9006420 39.4412870, -106.8998640 39.4408170, -106.8992700 39.4403320, -106.8985490 39.4398610, -106.8981740 39.4396250, -106.8966800 39.4388480, -106.8962060 39.4385930, -106.8958880 39.4383560, -106.8955140 39.4381650, -106.8950300 39.4380960, -106.8945130 39.4381350, -106.8939610 39.4383550, -106.8931260 39.4389190, -106.8926710 39.4393770, -106.8923180 39.4398130, -106.8923010 39.4398330, -106.8918830 39.4400820, -106.8911670 39.4403350, -106.8904610 39.4404240, -106.8894140 39.4404640, -106.8889430 39.4403620, -106.8883440 39.4402070, -106.8876350 39.4401750, -106.8871420 39.4402820, -106.8864700 39.4405900, -106.8859690 39.4409060, -106.8855560 39.4413520, -106.8852780 39.4415650, -106.8849130 39.4416920, -106.8843080 39.4418450, -106.8829360 39.4419010, -106.8823540 39.4418670, -106.8817150 39.4418220, -106.8813730 39.4417620, -106.8803760 39.4415260, -106.8798310 39.4413050, -106.8791570 39.4410080, -106.8786930 39.4406090, -106.8783710 39.4402300, -106.8775590 39.4389800, -106.8771890 39.4384040, -106.8770520 39.4380550, -106.8769020 39.4377280, -106.8768750 39.4372560, -106.8767900 39.4362030, -106.8766520 39.4357990, -106.8764480 39.4355930, -106.8761460 39.4354230, -106.8758600 39.4353170, -106.8756030 39.4352560, -106.8745410 39.4350380, -106.8729340 39.4348100, -106.8713690 39.4345600, -106.8701140 39.4342170, -106.8692870 39.4340210, -106.8685560 39.4337030, -106.8681970 39.4335330, -106.8677710 39.4334850, -106.8673630 39.4336010, -106.8671930 39.4335930, -106.8669190 39.4334210, -106.8668100 39.4333730)))";

  void assertRelation(
      std::string_view relation,
      std::optional<std::string_view> leftWkt,
      std::optional<std::string_view> rightWkt,
      bool expected) {
    std::optional<bool> actual = evaluateOnce<bool>(
        fmt::format(
            "{}(ST_GeometryFromText(c0), ST_GeometryFromText(c1))", relation),
        leftWkt,
        rightWkt);
    if (leftWkt.has_value() && rightWkt.has_value()) {
      EXPECT_TRUE(actual.has_value());
      EXPECT_EQ(actual.value(), expected);
    } else {
      EXPECT_FALSE(actual.has_value());
    }
  };

  void assertOverlay(
      std::string_view overlay,
      std::optional<std::string_view> leftWkt,
      std::optional<std::string_view> rightWkt,
      std::optional<std::string_view> expectedWkt) {
    // We are forced to make expectedWkt optional based on type signature, but
    // we always want to supply a value.
    std::optional<bool> actual = evaluateOnce<bool>(
        fmt::format(
            "ST_Equals({}(ST_GeometryFromText(c0), ST_GeometryFromText(c1)), ST_GeometryFromText(c2))",
            overlay),
        leftWkt,
        rightWkt,
        expectedWkt);
    if (leftWkt.has_value() && rightWkt.has_value()) {
      assert(expectedWkt.has_value());
      EXPECT_TRUE(actual.has_value());
      EXPECT_TRUE(actual.value());
    } else {
      EXPECT_FALSE(actual.has_value());
    }
  }
  facebook::velox::RowVectorPtr makeSingleStringInputRow(
      std::optional<std::string> input) {
    auto vec = makeNullableFlatVector<std::string>({input});
    return makeRowVector({vec});
  }

  bool aboutEquals(double a, double b) {
    double diff = std::abs(a - b);
    double scale = std::max(std::abs(a), std::abs(b));
    return diff <= std::max(1e-5, 1e-5 * scale);
  }
};

TEST_F(GeometryFunctionsTest, errorStGeometryFromTextAndParsing) {
  const auto assertGeomFromText = [&](const std::optional<std::string>& a) {
    return evaluateOnce<std::string>("ST_GeometryFromText(c0)", a);
  };
  const auto assertGeomFromBinary = [&](const std::optional<std::string>& wkt) {
    return evaluateOnce<std::string>(
        "to_hex(ST_AsBinary(ST_GeomFromBinary(from_hex(c0))))", wkt);
  };

  VELOX_ASSERT_USER_THROW(
      assertGeomFromText("xyz"),
      "Failed to parse WKT: ParseException: Unknown type: 'XYZ'");
  VELOX_ASSERT_USER_THROW(
      assertGeomFromText("LINESTRING (-71.3839245 42.3128124)"),
      "Failed to parse WKT: IllegalArgumentException: point array must contain 0 or >1 elements");
  VELOX_ASSERT_USER_THROW(
      assertGeomFromText(
          "POLYGON ((-13.637339 9.617113, -13.637339 9.617113))"),
      "Failed to parse WKT: IllegalArgumentException: Invalid number of points in LinearRing found 2 - must be 0 or >= 4");
  VELOX_ASSERT_USER_THROW(
      assertGeomFromText("POLYGON(0 0)"),
      "Failed to parse WKT: ParseException: Expected word but encountered number: '0'");
  VELOX_ASSERT_USER_THROW(
      assertGeomFromText("POLYGON((0 0))"),
      "Failed to parse WKT: IllegalArgumentException: point array must contain 0 or >1 elements");

  // WKT invalid cases
  VELOX_ASSERT_USER_THROW(
      assertGeomFromText(""), "Expected word but encountered end of stream");
  VELOX_ASSERT_USER_THROW(
      assertGeomFromText("RANDOM_TEXT"), "Unknown type: 'RANDOM_TEXT'");
  VELOX_ASSERT_USER_THROW(
      assertGeomFromText("LINESTRING (1 1)"),
      "point array must contain 0 or >1 elements");
  VELOX_ASSERT_USER_THROW(
      assertGeomFromText("LINESTRING ()"),
      "Expected number but encountered ')'");
  VELOX_ASSERT_USER_THROW(
      assertGeomFromText("POLYGON ((0 0, 0 0))"),
      "Invalid number of points in LinearRing found 2 - must be 0 or >= 4");
  VELOX_ASSERT_USER_THROW(
      assertGeomFromText("POLYGON ((0 0, 0 1, 1 1, 1 0))"),
      "Points of LinearRing do not form a closed linestring");

  // WKB invalid cases
  // Empty
  VELOX_ASSERT_USER_THROW(
      assertGeomFromBinary(""), "Unexpected EOF parsing WKB");

  // Random bytes
  VELOX_ASSERT_USER_THROW(
      assertGeomFromBinary("ABCDEF"), "Unexpected EOF parsing WKB");

  // Unrecognized geometry type
  VELOX_ASSERT_USER_THROW(
      assertGeomFromBinary("0109000000000000000000F03F0000000000000040"),
      "Unknown WKB type 9");

  // Point with missing y
  VELOX_ASSERT_USER_THROW(
      assertGeomFromBinary("0101000000000000000000F03F"),
      "Unexpected EOF parsing WKB");

  // LineString with only one point
  VELOX_ASSERT_THROW(
      assertGeomFromBinary(
          "010200000001000000000000000000F03F000000000000F03F"),
      "point array must contain 0 or >1 elements");

  // Polygon with unclosed LinString
  VELOX_ASSERT_THROW(
      assertGeomFromBinary(
          "01030000000100000004000000000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F000000000000F03F0000000000000000"),
      "Points of LinearRing do not form a closed linestring");

  VELOX_ASSERT_THROW(
      assertGeomFromBinary(
          "010300000001000000020000000000000000000000000000000000000000000000000000000000000000000000"),
      "Invalid number of points in LinearRing found 2 - must be 0 or >= 4");
}

TEST_F(GeometryFunctionsTest, wktAndWkb) {
  const auto wktRoundTrip = [&](const std::optional<std::string>& a) {
    return evaluateOnce<std::string>("ST_AsText(ST_GeometryFromText(c0))", a);
  };

  const auto wktToWkb = [&](const std::optional<std::string>& wkt) {
    return evaluateOnce<std::string>(
        "to_hex(ST_AsBinary(ST_GeometryFromText(c0)))", wkt);
  };

  const auto wkbToWkT = [&](const std::optional<std::string>& wkb) {
    return evaluateOnce<std::string>(
        "ST_AsText(ST_GeomFromBinary(from_hex(c0)))", wkb);
  };

  const auto wkbRoundTrip = [&](const std::optional<std::string>& wkt) {
    return evaluateOnce<std::string>(
        "to_hex(ST_AsBinary(ST_GeomFromBinary(from_hex(c0))))", wkt);
  };

  const auto wktRoundTripSpherical = [&](const std::optional<std::string>& a) {
    return evaluateOnce<std::string>(
        "ST_AsText(to_spherical_geography(ST_GeometryFromText(c0)))", a);
  };

  const std::vector<std::string> wkts = {
      "POINT (1 2)",
      "LINESTRING (0 0, 10 10)",
      "POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0))",
      "POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), (1 1, 4 1, 4 4, 1 4, 1 1))",
      "MULTIPOINT (1 2, 3 4)",
      "MULTILINESTRING ((0 0, 1 1), (2 2, 3 3))",
      "MULTIPOLYGON (((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2 2)))",
      "MULTIPOLYGON (((0 0, 0 4, 4 4, 4 0, 0 0), (1 1, 3 1, 3 3, 1 3, 1 1)), ((5 5, 5 7, 7 7, 7 5, 5 5)))",
      "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))",
      "GEOMETRYCOLLECTION (POINT (1 1), GEOMETRYCOLLECTION (LINESTRING (0 0, 1 1), GEOMETRYCOLLECTION (POLYGON ((2 2, 2 3, 3 3, 3 2, 2 2)))))",
      "GEOMETRYCOLLECTION (MULTILINESTRING ((0 0, 1 1), (2 2, 3 3)), MULTIPOLYGON (((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2 2))))"};

  const std::vector<std::string> wkbs = {"0101000000000000000000F03F0000000000000040", "0102000000020000000000000000000000000000000000000000000000000024400000000000002440", "010300000001000000050000000000000000000000000000000000000000000000000000000000000000001440000000000000144000000000000014400000000000001440000000000000000000000000000000000000000000000000", "01030000000200000005000000000000000000000000000000000000000000000000000000000000000000144000000000000014400000000000001440000000000000144000000000000000000000000000000000000000000000000005000000000000000000F03F000000000000F03F0000000000001040000000000000F03F00000000000010400000000000001040000000000000F03F0000000000001040000000000000F03F000000000000F03F", "0104000000020000000101000000000000000000F03F0000000000000040010100000000000000000008400000000000001040", "01050000000200000001020000000200000000000000000000000000000000000000000000000000F03F000000000000F03F0102000000020000000000000000000040000000000000004000000000000008400000000000000840", "01060000000200000001030000000100000005000000000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F000000000000F03F000000000000000000000000000000000000000000000000010300000001000000050000000000000000000040000000000000004000000000000000400000000000000840000000000000084000000000000008400000000000000840000000000000004000000000000000400000000000000040", "01060000000200000001030000000200000005000000000000000000000000000000000000000000000000000000000000000000104000000000000010400000000000001040000000000000104000000000000000000000000000000000000000000000000005000000000000000000F03F000000000000F03F0000000000000840000000000000F03F00000000000008400000000000000840000000000000F03F0000000000000840000000000000F03F000000000000F03F010300000001000000050000000000000000001440000000000000144000000000000014400000000000001C400000000000001C400000000000001C400000000000001C40000000000000144000000000000014400000000000001440", "0107000000020000000101000000000000000000F03F00000000000000400102000000020000000000000000000840000000000000104000000000000014400000000000001840", "0107000000020000000101000000000000000000F03F000000000000F03F01070000000200000001020000000200000000000000000000000000000000000000000000000000F03F000000000000F03F010700000001000000010300000001000000050000000000000000000040000000000000004000000000000000400000000000000840000000000000084000000000000008400000000000000840000000000000004000000000000000400000000000000040", "01070000000200000001050000000200000001020000000200000000000000000000000000000000000000000000000000F03F000000000000F03F010200000002000000000000000000004000000000000000400000000000000840000000000000084001060000000200000001030000000100000005000000000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F000000000000F03F000000000000000000000000000000000000000000000000010300000001000000050000000000000000000040000000000000004000000000000000400000000000000840000000000000084000000000000008400000000000000840000000000000004000000000000000400000000000000040"};

  const std::vector<std::string> bigEndianWkbs = {
      "00000000013FF00000000000004000000000000000",
      "0000000002000000020000000000000000000000000000000040240000000000004024000000000000",
      "000000000300000001000000050000000000000000000000000000000000000000000000004014000000000000401400000000000040140000000000004014000000000000000000000000000000000000000000000000000000000000",
      "000000000300000002000000050000000000000000000000000000000000000000000000004014000000000000401400000000000040140000000000004014000000000000000000000000000000000000000000000000000000000000000000053FF00000000000003FF000000000000040100000000000003FF0000000000000401000000000000040100000000000003FF000000000000040100000000000003FF00000000000003FF0000000000000",
      "00000000040000000200000000013FF00000000000004000000000000000000000000140080000000000004010000000000000",
      "000000000500000002000000000200000002000000000000000000000000000000003FF00000000000003FF00000000000000000000002000000024000000000000000400000000000000040080000000000004008000000000000",
      "000000000600000002000000000300000001000000050000000000000000000000000000000000000000000000003FF00000000000003FF00000000000003FF00000000000003FF0000000000000000000000000000000000000000000000000000000000000000000000300000001000000054000000000000000400000000000000040000000000000004008000000000000400800000000000040080000000000004008000000000000400000000000000040000000000000004000000000000000",
      "000000000600000002000000000300000002000000050000000000000000000000000000000000000000000000004010000000000000401000000000000040100000000000004010000000000000000000000000000000000000000000000000000000000000000000053FF00000000000003FF00000000000003FF000000000000040080000000000004008000000000000400800000000000040080000000000003FF00000000000003FF00000000000003FF000000000000000000000030000000100000005401400000000000040140000000000004014000000000000401c000000000000401c000000000000401c000000000000401c000000000000401400000000000040140000000000004014000000000000",
      "00000000070000000200000000013FF000000000000040000000000000000000000002000000024008000000000000401000000000000040140000000000004018000000000000",
      "00000000070000000200000000013FF00000000000003FF0000000000000000000000700000002000000000200000002000000000000000000000000000000003FF00000000000003FF0000000000000000000000700000001000000000300000001000000054000000000000000400000000000000040080000000000004000000000000000400800000000000040080000000000004000000000000000400800000000000040000000000000004000000000000000",
      //      "000000000700000002000000000500000002000000000200000002000000000000000000000000000000003FF00000000000003FF0000000000000000000000200000002400000000000000040000000000000004008000000000000400800000000000000000000060000000200000000030000000100000005000000000000000000000000000000000000000000000000000000000000f03f000000000000f03f000000000000f03f000000000000f03f000000000000000000000000000000000000000000000000010300000001000000050000000000000000000040000000000000004000000000000000400000000000000840000000000000084000000000000008400000000000000840000000000000004000000000000000400000000000000040"
      "000000000700000002000000000500000002000000000200000002000000000000000000000000000000003FF00000000000003FF00000000000000000000002000000024000000000000000400000000000000040080000000000004008000000000000000000000600000002000000000300000001000000050000000000000000000000000000000000000000000000003FF00000000000003FF00000000000003FF00000000000003FF0000000000000000000000000000000000000000000000000000000000000000000000300000001000000054000000000000000400000000000000040000000000000004008000000000000400800000000000040080000000000004008000000000000400000000000000040000000000000004000000000000000"};

  for (size_t i = 0; i < wkts.size(); i++) {
    assert(i < wkbs.size() && i < bigEndianWkbs.size());
    EXPECT_EQ(wkts[i], wktRoundTrip(wkts[i]));
    EXPECT_EQ(wkts[i], wktRoundTripSpherical(wkts[i]));
    EXPECT_EQ(wkbs[i], wktToWkb(wkts[i]));
    EXPECT_EQ(wkts[i], wkbToWkT(wkbs[i]));
    EXPECT_EQ(wkbs[i], wkbRoundTrip(wkbs[i]));

    EXPECT_EQ(wkbs[i], wkbRoundTrip(bigEndianWkbs[i]));
    EXPECT_EQ(wkts[i], wkbToWkT(bigEndianWkbs[i]));
  }

  const std::vector<std::string> emptyGeometryWkts = {
      "POINT EMPTY",
      "LINESTRING EMPTY",
      "POLYGON EMPTY",
      "MULTIPOINT EMPTY",
      "MULTILINESTRING EMPTY",
      "MULTIPOLYGON EMPTY",
      "GEOMETRYCOLLECTION EMPTY"};

  const std::vector<std::string> emptyGeometryWkbs = {
      "0101000000000000000000F87F000000000000F87F",
      "010200000000000000",
      "010300000000000000",
      "010400000000000000",
      "010500000000000000",
      "010600000000000000",
      "010700000000000000"};

  for (size_t i = 0; i < emptyGeometryWkts.size(); i++) {
    assert(i < emptyGeometryWkbs.size());
    EXPECT_EQ(wktRoundTrip(emptyGeometryWkts[i]), emptyGeometryWkts[i]);
    EXPECT_EQ(
        wktRoundTripSpherical(emptyGeometryWkts[i]), emptyGeometryWkts[i]);
    EXPECT_EQ(emptyGeometryWkbs[i], wktToWkb(emptyGeometryWkts[i]));
    EXPECT_EQ(emptyGeometryWkts[i], wkbToWkT(emptyGeometryWkbs[i]));
    EXPECT_EQ(emptyGeometryWkbs[i], wkbRoundTrip(emptyGeometryWkbs[i]));
  }
  wktRoundTrip(
      "POLYGON ((73.3644 -53.0331, 73.3652 -53.0327, 73.3661 -53.0327, 73.3677 -53.0323, 73.3686 -53.0324, 73.3691 -53.0326, 73.3698 -53.0333, 73.3699 -53.0342, 73.3692 -53.0346, 73.3695 -53.0348, 73.3698 -53.0344, 73.3707 -53.0345, 73.3713 -53.034, 73.3718 -53.0339, 73.3722 -53.0329, 73.3713 -53.0323, 73.3711 -53.0319, 73.3701 -53.0311, 73.3685 -53.0305, 73.3666 -53.0302, 73.366 -53.0299, 73.3655 -53.03, 73.3653 -53.0304, 73.3646 -53.031, 73.3636 -53.0308, 73.363 -53.0311, 73.3629 -53.0313, 73.3627 -53.0316, 73.3619 -53.0315, 73.3611 -53.0318, 73.3611 -53.0322, 73.3606 -53.0327, 73.3601 -53.0326, 73.3603 -53.0316, 73.361 -53.0312, 73.3617 -53.0311, 73.362 -53.0308, 73.3625 -53.0307, 73.3646 -53.03, 73.3656 -53.0293, 73.3669 -53.0293, 73.3684 -53.0301, 73.3701 -53.0304, 73.3705 -53.0298, 73.3711 -53.0293, 73.3712 -53.029, 73.3717 -53.0287, 73.3717 -53.0284, 73.3722 -53.0278, 73.3722 -53.0271, 73.3721 -53.0268, 73.3718 -53.0267, 73.3719 -53.0263, 73.373 -53.0266, 73.3735 -53.0273, 73.3761 -53.0272, 73.3765 -53.0274, 73.377 -53.0272, 73.3781 -53.0272, 73.3792 -53.0267, 73.3798 -53.0266, 73.3801 -53.0264, 73.3806 -53.0263, 73.3808 -53.0257, 73.3811 -53.0256, 73.3815 -53.0257, 73.3815 -53.0275, 73.3818 -53.0277, 73.3817 -53.028, 73.382 -53.0282, 73.382 -53.0284, 73.3815 -53.0287, 73.3825 -53.0288, 73.3831 -53.0284, 73.3839 -53.0286, 73.3847 -53.0284, 73.3854 -53.0285, 73.3872 -53.028, 73.3879 -53.0276, 73.3887 -53.0275, 73.3892 -53.0272, 73.3897 -53.0272, 73.3908 -53.0265, 73.3912 -53.0265, 73.3915 -53.0263, 73.3919 -53.0265, 73.3918 -53.027, 73.3911 -53.0275, 73.3905 -53.0276, 73.3905 -53.0278, 73.3901 -53.0279, 73.3902 -53.0282, 73.3898 -53.0285, 73.3885 -53.0287, 73.3874 -53.0291, 73.3874 -53.0293, 73.3872 -53.0294, 73.3862 -53.0292, 73.3839 -53.0293, 73.3836 -53.0291, 73.3832 -53.0294, 73.3836 -53.0298, 73.3843 -53.0297, 73.3847 -53.0299, 73.3849 -53.0296, 73.3853 -53.0296, 73.3858 -53.0299, 73.3862 -53.0299, 73.3861 -53.0295, 73.3866 -53.0293, 73.3868 -53.0294, 73.3869 -53.0296, 73.3866 -53.0299, 73.3863 -53.0299, 73.3865 -53.03, 73.3874 -53.0298, 73.388 -53.0298, 73.3882 -53.0293, 73.3891 -53.0292, 73.3898 -53.0292, 73.39 -53.0291, 73.39 -53.0288, 73.3903 -53.0286, 73.3908 -53.0285, 73.391 -53.0281, 73.3914 -53.028, 73.3923 -53.0284, 73.3923 -53.0292, 73.3926 -53.0294, 73.3926 -53.0296, 73.3922 -53.0297, 73.3916 -53.0303, 73.3916 -53.0306, 73.392 -53.0309, 73.3936 -53.0309, 73.3941 -53.0307, 73.3946 -53.031, 73.3947 -53.0308, 73.3951 -53.0307, 73.3954 -53.0309, 73.3952 -53.0312, 73.3955 -53.0313, 73.3965 -53.0316, 73.3977 -53.0317, 73.3979 -53.0313, 73.3977 -53.0309, 73.3972 -53.0307, 73.3968 -53.0301, 73.3962 -53.0297, 73.396 -53.029, 73.3951 -53.029, 73.3947 -53.0286, 73.3933 -53.0282, 73.393 -53.028, 73.3929 -53.0276, 73.3922 -53.0271, 73.3923 -53.0265, 73.3921 -53.0261, 73.3923 -53.0259, 73.3923 -53.0253, 73.3928 -53.0249, 73.3931 -53.024, 73.393 -53.0235, 73.3933 -53.023, 73.3931 -53.0228, 73.3931 -53.0222, 73.3937 -53.0221, 73.3928 -53.0218, 73.3925 -53.021, 73.3917 -53.0203, 73.3909 -53.0198, 73.3906 -53.0197, 73.3897 -53.0201, 73.3894 -53.0202, 73.3892 -53.02, 73.3893 -53.0195, 73.3901 -53.0193, 73.3905 -53.019, 73.3913 -53.019, 73.3916 -53.0185, 73.3927 -53.0184, 73.3936 -53.0177, 73.3943 -53.0175, 73.3944 -53.017, 73.3952 -53.0161, 73.3956 -53.016, 73.396 -53.0155, 73.3967 -53.0151, 73.3971 -53.015, 73.3974 -53.0147, 73.3985 -53.0143, 73.3995 -53.0142, 73.4 -53.0143, 73.4002 -53.0147, 73.4006 -53.0147, 73.4008 -53.0145, 73.4007 -53.014, 73.4011 -53.0138, 73.402 -53.0136, 73.4031 -53.0137, 73.404 -53.0132, 73.4054 -53.0133, 73.4063 -53.0138, 73.4071 -53.0135, 73.4078 -53.0137, 73.4083 -53.0136, 73.4084 -53.0131, 73.4087 -53.0129, 73.4093 -53.0129, 73.4106 -53.0132, 73.4116 -53.0128, 73.4118 -53.0122, 73.4109 -53.0121, 73.4104 -53.0117, 73.4104 -53.0114, 73.4109 -53.0108, 73.4118 -53.0106, 73.4118 -53.0103, 73.4114 -53.01, 73.4116 -53.009, 73.412 -53.0087, 73.4129 -53.0089, 73.4131 -53.0086, 73.4134 -53.0083, 73.414 -53.0083, 73.4145 -53.0085, 73.4145 -53.0082, 73.4147 -53.008, 73.4154 -53.008, 73.4158 -53.0085, 73.4157 -53.009, 73.4154 -53.0092, 73.415 -53.0091, 73.4147 -53.0089, 73.4147 -53.009, 73.4155 -53.0095, 73.4156 -53.0112, 73.4158 -53.0114, 73.4159 -53.0122, 73.4167 -53.013, 73.4165 -53.0144, 73.4162 -53.0148, 73.4136 -53.0163, 73.4135 -53.0169, 73.413 -53.0169, 73.4126 -53.0168, 73.4117 -53.0173, 73.41 -53.0184, 73.4085 -53.0198, 73.4085 -53.0204, 73.4083 -53.0207, 73.4085 -53.0209, 73.4085 -53.0212, 73.4092 -53.0222, 73.4102 -53.0231, 73.4105 -53.0235, 73.4104 -53.0237, 73.4097 -53.024, 73.4091 -53.0247, 73.4095 -53.0247, 73.4095 -53.025, 73.4102 -53.025, 73.4101 -53.0253, 73.4097 -53.0254, 73.4098 -53.0259, 73.4103 -53.0263, 73.4106 -53.0263, 73.4102 -53.0256, 73.4103 -53.0251, 73.4109 -53.0251, 73.4112 -53.0247, 73.4123 -53.0248, 73.4131 -53.0254, 73.4137 -53.0255, 73.4142 -53.0258, 73.4149 -53.0258, 73.4152 -53.0263, 73.4162 -53.0262, 73.4164 -53.0265, 73.4169 -53.0264, 73.4173 -53.0267, 73.4186 -53.0268, 73.419 -53.0271, 73.42 -53.0274, 73.4208 -53.0274, 73.4222 -53.0279, 73.4224 -53.0283, 73.4222 -53.0284, 73.4207 -53.0286, 73.4202 -53.029, 73.4203 -53.0294, 73.4211 -53.0296, 73.4212 -53.0301, 73.4201 -53.0311, 73.4184 -53.0318, 73.4178 -53.0318, 73.4175 -53.0316, 73.4167 -53.0318, 73.4161 -53.0325, 73.4149 -53.0325, 73.4144 -53.033, 73.414 -53.0331, 73.4138 -53.0334, 73.414 -53.0336, 73.4148 -53.0334, 73.4151 -53.0336, 73.4146 -53.0353, 73.4153 -53.0362, 73.4152 -53.0377, 73.4155 -53.0379, 73.4155 -53.0382, 73.4153 -53.0401, 73.4134 -53.0412, 73.4109 -53.042, 73.4096 -53.044, 73.4101 -53.0462, 73.4117 -53.0477, 73.4122 -53.0485, 73.4119 -53.0493, 73.4101 -53.0511, 73.4101 -53.0517, 73.4107 -53.0523, 73.4103 -53.0535, 73.4132 -53.0569, 73.4154 -53.0579, 73.4161 -53.0579, 73.4163 -53.0583, 73.4171 -53.0587, 73.423 -53.0615, 73.4235 -53.0618, 73.4237 -53.0621, 73.4246 -53.062, 73.4258 -53.0624, 73.426 -53.0628, 73.4266 -53.0627, 73.427 -53.063, 73.427 -53.0632, 73.4267 -53.0635, 73.4276 -53.0631, 73.4281 -53.0631, 73.4287 -53.0637, 73.4294 -53.0637, 73.4297 -53.0638, 73.4299 -53.0642, 73.4291 -53.0646, 73.429 -53.0651, 73.4286 -53.0652, 73.4285 -53.0657, 73.428 -53.0661, 73.4274 -53.0659, 73.4274 -53.0654, 73.427 -53.0651, 73.4273 -53.0648, 73.4281 -53.0647, 73.4276 -53.0644, 73.4271 -53.0643, 73.427 -53.064, 73.4265 -53.0639, 73.4265 -53.0641, 73.4267 -53.0643, 73.4267 -53.0645, 73.4251 -53.0653, 73.4242 -53.0652, 73.4237 -53.0646, 73.4225 -53.0648, 73.4221 -53.0647, 73.4219 -53.0644, 73.4215 -53.0644, 73.4212 -53.0642, 73.4205 -53.0643, 73.4198 -53.0639, 73.4198 -53.0637, 73.4192 -53.0638, 73.4188 -53.0635, 73.4189 -53.0632, 73.4194 -53.0632, 73.4194 -53.0624, 73.4191 -53.0621, 73.4185 -53.0621, 73.4177 -53.0625, 73.4174 -53.0628, 73.4166 -53.0627, 73.4159 -53.0624, 73.4158 -53.0621, 73.4151 -53.0621, 73.4142 -53.0617, 73.4138 -53.0614, 73.4111 -53.0603, 73.4105 -53.0595, 73.4098 -53.0592, 73.4094 -53.0593, 73.4087 -53.0593, 73.4071 -53.0577, 73.4054 -53.0575, 73.4049 -53.057, 73.4039 -53.0567, 73.4024 -53.0554, 73.4011 -53.0552, 73.4005 -53.0549, 73.3994 -53.0553, 73.3976 -53.0553, 73.3972 -53.055, 73.3963 -53.0549, 73.3962 -53.055, 73.3964 -53.0552, 73.3964 -53.0556, 73.3955 -53.0562, 73.3946 -53.0562, 73.394 -53.0557, 73.3937 -53.0557, 73.3937 -53.0561, 73.3942 -53.0564, 73.3942 -53.0569, 73.3938 -53.0572, 73.3906 -53.0582, 73.3894 -53.0584, 73.3891 -53.0586, 73.3883 -53.0586, 73.3882 -53.0589, 73.3877 -53.0593, 73.3859 -53.0606, 73.3851 -53.0608, 73.3848 -53.0606, 73.3848 -53.0602, 73.385 -53.06, 73.385 -53.0596, 73.3848 -53.0594, 73.384 -53.0591, 73.3836 -53.0595, 73.3834 -53.0615, 73.384 -53.0616, 73.3842 -53.0618, 73.3842 -53.0624, 73.384 -53.0626, 73.3841 -53.0628, 73.3841 -53.0646, 73.384 -53.0648, 73.384 -53.0657, 73.3867 -53.0656, 73.3873 -53.0657, 73.3879 -53.0652, 73.389 -53.0655, 73.3891 -53.0661, 73.3886 -53.0665, 73.3884 -53.0668, 73.3885 -53.0674, 73.3888 -53.0676, 73.3888 -53.0679, 73.3884 -53.0683, 73.3893 -53.0687, 73.3893 -53.0695, 73.3888 -53.0697, 73.3888 -53.0702, 73.389 -53.0703, 73.3896 -53.0703, 73.3901 -53.07, 73.3906 -53.0701, 73.3908 -53.0702, 73.3908 -53.0708, 73.3915 -53.0708, 73.3919 -53.0712, 73.392 -53.0716, 73.3916 -53.0719, 73.3923 -53.0724, 73.3927 -53.0729, 73.3925 -53.0734, 73.3919 -53.0734, 73.3913 -53.0739, 73.3907 -53.074, 73.3904 -53.0744, 73.3893 -53.0748, 73.3885 -53.0748, 73.3883 -53.0753, 73.3872 -53.076, 73.3867 -53.076, 73.3861 -53.0768, 73.3855 -53.0771, 73.3855 -53.0773, 73.3863 -53.0779, 73.3863 -53.079, 73.386 -53.0795, 73.3853 -53.0796, 73.385 -53.0792, 73.3834 -53.0781, 73.3818 -53.0775, 73.3808 -53.0769, 73.3794 -53.0764, 73.3788 -53.0764, 73.3776 -53.0768, 73.3775 -53.0771, 73.3773 -53.0773, 73.3768 -53.0773, 73.3763 -53.0776, 73.3758 -53.0775, 73.3757 -53.0771, 73.3764 -53.0769, 73.3756 -53.0769, 73.3755 -53.0766, 73.3757 -53.0764, 73.3762 -53.0763, 73.3765 -53.0761, 73.3759 -53.0759, 73.3758 -53.0755, 73.375 -53.0751, 73.375 -53.0747, 73.3749 -53.0746, 73.3741 -53.0744, 73.3735 -53.0747, 73.3729 -53.0745, 73.3727 -53.0741, 73.3727 -53.0733, 73.3731 -53.073, 73.3736 -53.0729, 73.3733 -53.0726, 73.3721 -53.0726, 73.3718 -53.0724, 73.3708 -53.0723, 73.3689 -53.0732, 73.367 -53.0737, 73.3654 -53.0738, 73.3645 -53.0738, 73.3638 -53.0743, 73.3638 -53.0748, 73.3625 -53.075, 73.3622 -53.0755, 73.3617 -53.0755, 73.3615 -53.0753, 73.3615 -53.0748, 73.3616 -53.0745, 73.3624 -53.0739, 73.3631 -53.0738, 73.3632 -53.0733, 73.3635 -53.073, 73.3654 -53.0726, 73.368 -53.0724, 73.3689 -53.0718, 73.3723 -53.0711, 73.374 -53.07, 73.3754 -53.0682, 73.3773 -53.0674, 73.3787 -53.0661, 73.3798 -53.0648, 73.3806 -53.0633, 73.3812 -53.0626, 73.3814 -53.062, 73.3819 -53.0614, 73.3817 -53.0609, 73.3818 -53.0604, 73.3816 -53.0597, 73.3818 -53.0592, 73.3824 -53.0587, 73.3824 -53.0581, 73.3827 -53.0579, 73.382 -53.057, 73.3821 -53.0565, 73.3815 -53.0561, 73.3816 -53.0556, 73.3812 -53.0552, 73.3812 -53.0549, 73.3816 -53.0548, 73.3815 -53.0538, 73.3818 -53.0535, 73.3813 -53.0533, 73.3813 -53.0529, 73.3818 -53.0525, 73.3815 -53.0522, 73.3812 -53.0516, 73.3807 -53.052, 73.38 -53.0518, 73.3799 -53.0511, 73.3796 -53.0509, 73.3795 -53.0505, 73.3796 -53.0498, 73.3806 -53.0499, 73.3802 -53.0497, 73.3804 -53.049, 73.3802 -53.0487, 73.3803 -53.0483, 73.3799 -53.0478, 73.3795 -53.0476, 73.3795 -53.0465, 73.3801 -53.0462, 73.3794 -53.046, 73.3786 -53.0453, 73.3779 -53.0454, 73.3777 -53.0453, 73.3778 -53.0451, 73.3774 -53.0451, 73.3773 -53.0454, 73.3769 -53.0454, 73.3768 -53.0451, 73.3772 -53.045, 73.3772 -53.0447, 73.3775 -53.0443, 73.3763 -53.0437, 73.3759 -53.0429, 73.3746 -53.0425, 73.3732 -53.0424, 73.3725 -53.0421, 73.3722 -53.0421, 73.3721 -53.0424, 73.3709 -53.0425, 73.3707 -53.0429, 73.3714 -53.0435, 73.3714 -53.0437, 73.3707 -53.0441, 73.3694 -53.0446, 73.3686 -53.0445, 73.3681 -53.0442, 73.3674 -53.0441, 73.3665 -53.0437, 73.3657 -53.0431, 73.3651 -53.0418, 73.3651 -53.0415, 73.3656 -53.041, 73.3661 -53.0402, 73.3676 -53.0396, 73.3678 -53.0393, 73.3696 -53.0393, 73.3701 -53.0392, 73.3702 -53.0389, 73.3708 -53.0388, 73.371 -53.0384, 73.371 -53.0378, 73.3706 -53.0376, 73.3705 -53.0371, 73.3709 -53.0364, 73.3699 -53.0356, 73.3697 -53.0351, 73.3693 -53.0353, 73.3688 -53.0353, 73.3682 -53.0349, 73.3677 -53.0349, 73.3674 -53.0345, 73.3669 -53.0345, 73.3667 -53.0342, 73.3648 -53.0339, 73.3654 -53.0344, 73.3654 -53.0347, 73.3644 -53.0349, 73.3639 -53.0351, 73.3625 -53.0351, 73.3616 -53.0348, 73.3614 -53.0343, 73.361 -53.0342, 73.3607 -53.0336, 73.3607 -53.0332, 73.3613 -53.0326, 73.3622 -53.0322, 73.3625 -53.0319, 73.363 -53.0319, 73.3633 -53.0321, 73.3632 -53.0327, 73.3634 -53.0329, 73.3634 -53.0334, 73.3643 -53.0333, 73.3644 -53.0331), (73.3938 -53.0223, 73.3938 -53.0222, 73.3937 -53.0221, 73.3938 -53.0222, 73.3938 -53.0223), (73.4095 -53.0252, 73.4094 -53.0253, 73.4095 -53.0253, 73.4095 -53.0253, 73.4095 -53.0252), (73.4162 -53.0267, 73.4163 -53.0267, 73.4163 -53.0266, 73.4162 -53.0267, 73.4162 -53.0267), (73.4259 -53.0629, 73.4259 -53.0631, 73.4263 -53.0632, 73.426 -53.063, 73.4259 -53.0629), (73.3805 -53.0503, 73.3801 -53.0504, 73.3806 -53.0505, 73.3806 -53.0508, 73.3807 -53.0508, 73.3809 -53.0505, 73.3805 -53.0503), (73.3809 -53.0512, 73.3811 -53.0513, 73.3812 -53.0514, 73.3812 -53.0512, 73.3809 -53.0512), (73.3783 -53.045, 73.378 -53.0449, 73.3778 -53.045, 73.378 -53.045, 73.3783 -53.045), (73.3899 -53.0275, 73.39 -53.0274, 73.3899 -53.0274, 73.3899 -53.0275, 73.3899 -53.0275), (73.3962 -53.0287, 73.3965 -53.0286, 73.3965 -53.0284, 73.397 -53.0279, 73.3969 -53.0273, 73.3965 -53.0269, 73.3971 -53.0267, 73.3968 -53.0266, 73.3967 -53.0263, 73.3972 -53.0263, 73.3974 -53.0266, 73.3989 -53.0263, 73.3988 -53.0262, 73.3983 -53.0262, 73.3982 -53.026, 73.3993 -53.0255, 73.3989 -53.025, 73.3987 -53.0249, 73.3986 -53.0253, 73.3978 -53.0255, 73.3976 -53.0254, 73.3976 -53.0252, 73.398 -53.0251, 73.3981 -53.0248, 73.3985 -53.0248, 73.397 -53.0246, 73.3962 -53.0249, 73.3959 -53.0252, 73.396 -53.0265, 73.3957 -53.0267, 73.3953 -53.0267, 73.3938 -53.026, 73.3935 -53.0261, 73.3936 -53.0263, 73.3941 -53.0264, 73.394 -53.0266, 73.3936 -53.0266, 73.3938 -53.0268, 73.3939 -53.0274, 73.395 -53.028, 73.395 -53.0284, 73.3962 -53.0287), (73.3825 -53.0548, 73.3828 -53.0554, 73.3828 -53.0559, 73.3834 -53.0562, 73.3837 -53.0566, 73.384 -53.0567, 73.3845 -53.0565, 73.385 -53.056, 73.3864 -53.055, 73.3871 -53.055, 73.3877 -53.0547, 73.3883 -53.0546, 73.3877 -53.054, 73.3877 -53.0537, 73.3879 -53.0535, 73.3896 -53.0531, 73.3899 -53.0529, 73.3905 -53.0529, 73.3917 -53.0532, 73.3921 -53.0536, 73.3928 -53.0538, 73.3929 -53.0537, 73.393 -53.0534, 73.3936 -53.053, 73.3948 -53.0528, 73.396 -53.053, 73.3968 -53.0526, 73.3981 -53.0526, 73.3985 -53.0525, 73.3982 -53.0522, 73.3985 -53.0513, 73.399 -53.0511, 73.3997 -53.0504, 73.4012 -53.0503, 73.4019 -53.0505, 73.403 -53.0514, 73.403 -53.0525, 73.4029 -53.0527, 73.403 -53.0531, 73.4038 -53.0536, 73.4042 -53.0545, 73.4052 -53.0549, 73.4056 -53.0546, 73.4054 -53.0526, 73.4056 -53.0518, 73.4061 -53.0512, 73.406 -53.0508, 73.4057 -53.0505, 73.4058 -53.05, 73.4054 -53.0497, 73.4054 -53.0493, 73.4048 -53.0487, 73.4048 -53.0483, 73.405 -53.0479, 73.4054 -53.0478, 73.4055 -53.0473, 73.4058 -53.0469, 73.4058 -53.0466, 73.4045 -53.0457, 73.4045 -53.0454, 73.4047 -53.0451, 73.4047 -53.045, 73.4039 -53.045, 73.4036 -53.0453, 73.403 -53.0455, 73.402 -53.0454, 73.4015 -53.0445, 73.4015 -53.0437, 73.4016 -53.0434, 73.4022 -53.0433, 73.4018 -53.043, 73.4015 -53.0429, 73.4011 -53.0432, 73.401 -53.0435, 73.4004 -53.0441, 73.4006 -53.0444, 73.4006 -53.0448, 73.4004 -53.0451, 73.3999 -53.0452, 73.3996 -53.0455, 73.3986 -53.0457, 73.3983 -53.0461, 73.396 -53.0459, 73.3954 -53.0461, 73.3952 -53.0466, 73.3937 -53.047, 73.3932 -53.0478, 73.3924 -53.0481, 73.3926 -53.0484, 73.3926 -53.0487, 73.3923 -53.0491, 73.3918 -53.0493, 73.3915 -53.0496, 73.3907 -53.0499, 73.3902 -53.0502, 73.3892 -53.0504, 73.3884 -53.051, 73.3876 -53.051, 73.3864 -53.0516, 73.3855 -53.0516, 73.3848 -53.052, 73.3845 -53.0524, 73.3843 -53.053, 73.3831 -53.0538, 73.3829 -53.0545, 73.3825 -53.0548))");
}

// Constructors and accessors

TEST_F(GeometryFunctionsTest, testStPoint) {
  const auto assertPoint = [&](const std::optional<double>& x,
                               const std::optional<double> y) {
    std::optional<double> actualX =
        evaluateOnce<double>("ST_X(ST_Point(c0, c1))", x, y);
    std::optional<double> actualY =
        evaluateOnce<double>("ST_Y(ST_Point(c0, c1))", x, y);
    if (x.has_value() && y.has_value()) {
      EXPECT_TRUE(actualX.has_value());
      EXPECT_TRUE(actualY.has_value());
      EXPECT_EQ(x.value(), actualX.value());
      EXPECT_EQ(y.value(), actualY.value());
    } else {
      EXPECT_FALSE(actualX.has_value());
      EXPECT_FALSE(actualY.has_value());
    }
  };

  assertPoint(std::nullopt, 0.0);
  assertPoint(0.0, 0.0);
  assertPoint(1.0, -23.12344);
  VELOX_ASSERT_THROW(
      assertPoint(0.0, NAN),
      "ST_Point requires finite coordinates, got x=0 y=nan");
  VELOX_ASSERT_THROW(
      assertPoint(INFINITY, 0.0),
      "ST_Point requires finite coordinates, got x=inf y=0");

  std::optional<double> nullX =
      evaluateOnce<double>("ST_X(ST_GeometryFromText('POINT EMPTY'))");
  EXPECT_FALSE(nullX.has_value());
  std::optional<double> nullY =
      evaluateOnce<double>("ST_Y(ST_GeometryFromText('POINT EMPTY'))");
  EXPECT_FALSE(nullY.has_value());
}

// Relationship predicates

TEST_F(GeometryFunctionsTest, testStRelate) {
  const auto assertStRelate =
      [&](std::optional<std::string_view> leftWkt,
          std::optional<std::string_view> rightWkt,
          std::optional<std::string_view> relateCondition,
          bool expected) {
        std::optional<bool> actual = evaluateOnce<bool>(
            "ST_Relate(ST_GeometryFromText(c0), ST_GeometryFromText(c1), c2)",
            leftWkt,
            rightWkt,
            relateCondition);
        if (leftWkt.has_value() && rightWkt.has_value() &&
            relateCondition.has_value()) {
          EXPECT_TRUE(actual.has_value());
          EXPECT_EQ(actual.value(), expected);
        } else {
          EXPECT_FALSE(actual.has_value());
        }
      };

  assertStRelate(
      "LINESTRING (0 0, 3 3)", "LINESTRING (1 1, 4 1)", "****T****", false);
  assertStRelate(
      "POLYGON ((2 0, 2 1, 3 1, 2 0))",
      "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))",
      "****T****",
      true);
  assertStRelate(
      "POLYGON ((2 0, 2 1, 3 1, 2 0))",
      "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))",
      "T********",
      false);
  assertStRelate(std::nullopt, std::nullopt, std::nullopt, false);
}

TEST_F(GeometryFunctionsTest, testStContains) {
  assertRelation(
      "ST_Contains",
      kRelationGeometriesWKT[1],
      kRelationGeometriesWKT[0],
      true);
  assertRelation(
      "ST_Contains",
      kRelationGeometriesWKT[2],
      kRelationGeometriesWKT[3],
      true);
  assertRelation(
      "ST_Contains",
      kRelationGeometriesWKT[4],
      kRelationGeometriesWKT[5],
      true);
  assertRelation(
      "ST_Contains",
      kRelationGeometriesWKT[1],
      kRelationGeometriesWKT[6],
      true);
  assertRelation(
      "ST_Contains",
      kRelationGeometriesWKT[2],
      kRelationGeometriesWKT[6],
      true);
  assertRelation(
      "ST_Contains",
      kRelationGeometriesWKT[2],
      kRelationGeometriesWKT[7],
      true);
  assertRelation(
      "ST_Contains",
      kRelationGeometriesWKT[3],
      kRelationGeometriesWKT[6],
      true);
  assertRelation(
      "ST_Contains",
      kRelationGeometriesWKT[3],
      kRelationGeometriesWKT[7],
      true);
  assertRelation(
      "ST_Contains",
      kRelationGeometriesWKT[4],
      kRelationGeometriesWKT[7],
      true);

  assertRelation("ST_Contains", std::nullopt, "POINT (25 25)", false);
  assertRelation("ST_Contains", "POINT (20 20)", "POINT (25 25)", false);
  assertRelation(
      "ST_Contains", "MULTIPOINT (20 20, 25 25)", "POINT (25 25)", true);
  assertRelation(
      "ST_Contains", "LINESTRING (20 20, 30 30)", "POINT (25 25)", true);
  assertRelation(
      "ST_Contains",
      "LINESTRING (20 20, 30 30)",
      "MULTIPOINT (25 25, 31 31)",
      false);
  assertRelation(
      "ST_Contains",
      "LINESTRING (20 20, 30 30)",
      "LINESTRING (25 25, 27 27)",
      true);
  assertRelation(
      "ST_Contains",
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))",
      "MULTILINESTRING ((3 4, 4 4), (2 1, 6 1))",
      false);
  assertRelation(
      "ST_Contains",
      "POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0))",
      "POLYGON ((1 1, 1 2, 2 2, 2 1, 1 1))",
      true);
  assertRelation(
      "ST_Contains",
      "POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0))",
      "POLYGON ((-1 -1, -1 2, 2 2, 2 -1, -1 -1))",
      false);
  assertRelation(
      "ST_Contains",
      "MULTIPOLYGON (((0 0, 0 2, 2 2, 2 0, 0 0)), ((2 2, 2 4, 4 4, 4 2, 2 2)))",
      "POLYGON ((2 2, 2 3, 3 3, 3 2, 2 2))",
      true);
  assertRelation(
      "ST_Contains",
      "LINESTRING (20 20, 30 30)",
      "POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0))",
      false);
  assertRelation(
      "ST_Contains",
      "LINESTRING EMPTY",
      "POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0))",
      false);
  assertRelation(
      "ST_Contains", "LINESTRING (20 20, 30 30)", "POLYGON EMPTY", false);

  VELOX_ASSERT_USER_THROW(
      assertRelation(
          "ST_Contains",
          "MULTIPOLYGON ( ((0 0, 0 2, 2 2, 2 0, 0 0)), ((1 1, 1 3, 3 3, 3 1, 1 1)) )",
          "POINT (1 1)",
          false),
      "TopologyException: side location conflict at 1 2. This can occur if the input geometry is invalid.");
}

TEST_F(GeometryFunctionsTest, testStCrosses) {
  assertRelation(
      "ST_Crosses", kRelationGeometriesWKT[1], kRelationGeometriesWKT[3], true);
  assertRelation(
      "ST_Crosses", kRelationGeometriesWKT[3], kRelationGeometriesWKT[1], true);
  assertRelation(
      "ST_Crosses", kRelationGeometriesWKT[2], kRelationGeometriesWKT[4], true);
  assertRelation(
      "ST_Crosses", kRelationGeometriesWKT[4], kRelationGeometriesWKT[2], true);
  assertRelation(
      "ST_Crosses", kRelationGeometriesWKT[2], kRelationGeometriesWKT[5], true);
  assertRelation(
      "ST_Crosses", kRelationGeometriesWKT[5], kRelationGeometriesWKT[2], true);
  assertRelation(
      "ST_Crosses", kRelationGeometriesWKT[3], kRelationGeometriesWKT[4], true);
  assertRelation(
      "ST_Crosses", kRelationGeometriesWKT[4], kRelationGeometriesWKT[3], true);

  assertRelation("ST_Crosses", std::nullopt, "POINT (25 25)", false);
  assertRelation("ST_Crosses", "POINT (20 20)", "POINT (25 25)", false);
  assertRelation(
      "ST_Crosses", "LINESTRING (20 20, 30 30)", "POINT (25 25)", false);
  assertRelation(
      "ST_Crosses",
      "LINESTRING (20 20, 30 30)",
      "MULTIPOINT (25 25, 31 31)",
      true);
  assertRelation(
      "ST_Crosses", "LINESTRING(0 0, 1 1)", "LINESTRING (1 0, 0 1)", true);
  assertRelation(
      "ST_Crosses",
      "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))",
      "POLYGON ((2 2, 2 5, 5 5, 5 2, 2 2))",
      false);
  assertRelation(
      "ST_Crosses",
      "MULTIPOLYGON (((0 0, 0 2, 2 2, 2 0, 0 0)), ((2 2, 2 4, 4 4, 4 2, 2 2)))",
      "POLYGON ((2 2, 2 3, 3 3, 3 2, 2 2))",
      false);
  assertRelation(
      "ST_Crosses",
      "LINESTRING (-2 -2, 6 6)",
      "POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0))",
      true);
  assertRelation("ST_Crosses", "POINT (20 20)", "POINT (20 20)", false);
  assertRelation(
      "ST_Crosses",
      "POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0))",
      "POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0))",
      false);
  assertRelation(
      "ST_Crosses",
      "POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0))",
      "LINESTRING (0 0, 0 4, 4 4, 4 0)",
      false);

  VELOX_ASSERT_USER_THROW(
      assertRelation(
          "ST_Crosses",
          "MULTIPOLYGON ( ((0 0, 0 2, 2 2, 2 0, 0 0)), ((1 1, 1 3, 3 3, 3 1, 1 1)) )",
          "POINT (1 1)",
          false),
      "TopologyException: side location conflict at 1 2. This can occur if the input geometry is invalid.");
}

TEST_F(GeometryFunctionsTest, testStDisjoint) {
  assertRelation("ST_Disjoint", std::nullopt, "POINT (150 150)", true);
  assertRelation("ST_Disjoint", "POINT (50 100)", "POINT (150 150)", true);
  assertRelation(
      "ST_Disjoint", "MULTIPOINT (50 100, 50 200)", "POINT (50 100)", false);
  assertRelation(
      "ST_Disjoint", "LINESTRING (0 0, 0 1)", "LINESTRING (1 1, 1 0)", true);
  assertRelation(
      "ST_Disjoint", "LINESTRING (2 1, 1 2)", "LINESTRING (3 1, 1 3)", true);
  assertRelation(
      "ST_Disjoint", "LINESTRING (1 1, 3 3)", "LINESTRING (3 1, 1 3)", false);
  assertRelation(
      "ST_Disjoint",
      "LINESTRING (50 100, 50 200)",
      "LINESTRING (20 150, 100 150)",
      false);
  assertRelation(
      "ST_Disjoint",
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))",
      "MULTILINESTRING ((3 4, 6 4), (5 0, 5 4))",
      false);
  assertRelation(
      "ST_Disjoint",
      "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
      "POLYGON ((4 4, 4 5, 5 5, 5 4, 4 4))",
      true);

  VELOX_ASSERT_USER_THROW(
      assertRelation(
          "ST_Disjoint",
          "MULTIPOLYGON ( ((0 0, 0 2, 2 2, 2 0, 0 0)), ((1 1, 1 3, 3 3, 3 1, 1 1)) )",
          "POINT (1 1)",
          false),
      "TopologyException: side location conflict at 1 2. This can occur if the input geometry is invalid.");
}

TEST_F(GeometryFunctionsTest, testStEquals) {
  for (const auto& leftWkt : kRelationGeometriesWKT) {
    for (const auto& rightWkt : kRelationGeometriesWKT) {
      assertRelation("ST_Equals", leftWkt, rightWkt, leftWkt == rightWkt);
    }
  }

  assertRelation("ST_Equals", std::nullopt, "POINT (150 150)", false);
  assertRelation("ST_Equals", "POINT (50 100)", "POINT (150 150)", false);
  assertRelation(
      "ST_Equals", "MULTIPOINT (50 100, 50 200)", "POINT (50 100)", false);
  assertRelation(
      "ST_Equals", "LINESTRING (0 0, 0 1)", "LINESTRING (1 1, 1 0)", false);
  assertRelation(
      "ST_Equals", "LINESTRING (0 0, 2 2)", "LINESTRING (0 0, 2 2)", true);
  assertRelation(
      "ST_Equals",
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))",
      "MULTILINESTRING ((3 4, 6 4), (5 0, 5 4))",
      false);
  assertRelation(
      "ST_Equals",
      "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
      "POLYGON ((3 3, 3 1, 1 1, 1 3, 3 3))",
      true);
  assertRelation(
      "ST_Equals",
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((0 0, 0 2, 2 2, 2 0, 0 0)))",
      "POLYGON ((0 1, 3 1, 3 3, 0 3, 0 1))",
      false);
  // Invalid geometries.  This test might have to change when upgrading GEOS.
  assertRelation(
      "ST_Equals",
      "MULTIPOLYGON ( ((0 0, 0 2, 2 2, 2 0, 0 0)), ((1 1, 1 3, 3 3, 3 1, 1 1)) )",
      "LINESTRING (0 0, 1 1, 1 0, 0 1)",
      false);
}

TEST_F(GeometryFunctionsTest, testStIntersects) {
  assertRelation("ST_Intersects", std::nullopt, "POINT (150 150)", false);
  assertRelation("ST_Intersects", "POINT (50 100)", "POINT (150 150)", false);
  assertRelation(
      "ST_Intersects", "MULTIPOINT (50 100, 50 200)", "POINT (50 100)", true);
  assertRelation(
      "ST_Intersects", "LINESTRING (0 0, 0 1)", "LINESTRING (1 1, 1 0)", false);
  assertRelation(
      "ST_Intersects",
      "LINESTRING (50 100, 50 200)",
      "LINESTRING (20 150, 100 150)",
      true);
  assertRelation(
      "ST_Intersects",
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))",
      "MULTILINESTRING ((3 4, 6 4), (5 0, 5 4))",
      true);
  assertRelation(
      "ST_Intersects",
      "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
      "POLYGON ((4 4, 4 5, 5 5, 5 4, 4 4))",
      false);
  assertRelation(
      "ST_Intersects",
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((0 0, 0 2, 2 2, 2 0, 0 0)))",
      "POLYGON ((0 1, 3 1, 3 3, 0 3, 0 1))",
      true);
  assertRelation(
      "ST_Intersects",
      "POLYGON ((16.5 54, 16.5 54.1, 16.51 54.1, 16.8 54, 16.5 54))",
      "LINESTRING (16.6 53, 16.6 56)",
      true);
  assertRelation(
      "ST_Intersects",
      "POLYGON ((16.5 54, 16.5 54.1, 16.51 54.1, 16.8 54, 16.5 54))",
      "LINESTRING (16.6667 54.05, 16.8667 54.05)",
      false);
  assertRelation(
      "ST_Intersects",
      "POLYGON ((16.5 54, 16.5 54.1, 16.51 54.1, 16.8 54, 16.5 54))",
      "LINESTRING (16.6667 54.25, 16.8667 54.25)",
      false);

  VELOX_ASSERT_USER_THROW(
      assertRelation(
          "ST_Intersects",
          "MULTIPOLYGON ( ((0 0, 0 2, 2 2, 2 0, 0 0)), ((1 1, 1 3, 3 3, 3 1, 1 1)) )",
          "POINT (1 1)",
          false),
      "TopologyException: side location conflict at 1 2. This can occur if the input geometry is invalid.");
}

TEST_F(GeometryFunctionsTest, testStOverlaps) {
  assertRelation(
      "ST_Overlaps",
      kRelationGeometriesWKT[1],
      kRelationGeometriesWKT[2],
      true);
  assertRelation(
      "ST_Overlaps",
      kRelationGeometriesWKT[2],
      kRelationGeometriesWKT[1],
      true);

  assertRelation("ST_Overlaps", std::nullopt, "POINT (150 150)", false);
  assertRelation("ST_Overlaps", "POINT (50 100)", "POINT (150 150)", false);
  assertRelation("ST_Overlaps", "POINT (50 100)", "POINT (50 100)", false);
  assertRelation(
      "ST_Overlaps", "MULTIPOINT (50 100, 50 200)", "POINT (50 100)", false);
  assertRelation(
      "ST_Overlaps", "LINESTRING (0 0, 0 1)", "LINESTRING (1 1, 1 0)", false);
  assertRelation(
      "ST_Overlaps",
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))",
      "MULTILINESTRING ((3 4, 6 4), (5 0, 5 4))",
      true);
  assertRelation(
      "ST_Overlaps",
      "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))",
      "POLYGON ((3 3, 3 5, 5 5, 5 3, 3 3))",
      true);
  assertRelation(
      "ST_Overlaps",
      "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))",
      "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))",
      false);
  assertRelation(
      "ST_Overlaps",
      "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))",
      "LINESTRING (1 1, 4 4)",
      false);
  assertRelation(
      "ST_Overlaps",
      "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
      "POLYGON ((4 4, 4 5, 5 5, 5 4, 4 4))",
      false);

  VELOX_ASSERT_USER_THROW(
      assertRelation(
          "ST_Overlaps",
          "MULTIPOLYGON ( ((0 0, 0 2, 2 2, 2 0, 0 0)), ((1 1, 1 3, 3 3, 3 1, 1 1)) )",
          "POINT (1 1)",
          false),
      "TopologyException: side location conflict at 1 2. This can occur if the input geometry is invalid.");
}

TEST_F(GeometryFunctionsTest, testStTouches) {
  assertRelation(
      "ST_Touches", kRelationGeometriesWKT[0], kRelationGeometriesWKT[2], true);
  assertRelation(
      "ST_Touches", kRelationGeometriesWKT[2], kRelationGeometriesWKT[0], true);
  assertRelation(
      "ST_Touches", kRelationGeometriesWKT[0], kRelationGeometriesWKT[3], true);
  assertRelation(
      "ST_Touches", kRelationGeometriesWKT[3], kRelationGeometriesWKT[0], true);
  assertRelation(
      "ST_Touches", kRelationGeometriesWKT[1], kRelationGeometriesWKT[4], true);
  assertRelation(
      "ST_Touches", kRelationGeometriesWKT[4], kRelationGeometriesWKT[1], true);
  assertRelation(
      "ST_Touches", kRelationGeometriesWKT[1], kRelationGeometriesWKT[5], true);
  assertRelation(
      "ST_Touches", kRelationGeometriesWKT[5], kRelationGeometriesWKT[1], true);
  assertRelation(
      "ST_Touches", kRelationGeometriesWKT[3], kRelationGeometriesWKT[5], true);
  assertRelation(
      "ST_Touches", kRelationGeometriesWKT[5], kRelationGeometriesWKT[3], true);
  assertRelation(
      "ST_Touches", kRelationGeometriesWKT[1], kRelationGeometriesWKT[7], true);
  assertRelation(
      "ST_Touches", kRelationGeometriesWKT[7], kRelationGeometriesWKT[1], true);
  assertRelation(
      "ST_Touches", kRelationGeometriesWKT[5], kRelationGeometriesWKT[7], true);
  assertRelation(
      "ST_Touches", kRelationGeometriesWKT[7], kRelationGeometriesWKT[5], true);

  assertRelation("ST_Touches", std::nullopt, "POINT (150 150)", false);
  assertRelation("ST_Touches", "POINT (50 100)", "POINT (150 150)", false);
  assertRelation(
      "ST_Touches", "MULTIPOINT (50 100, 50 200)", "POINT (50 100)", false);
  assertRelation(
      "ST_Touches",
      "LINESTRING (50 100, 50 200)",
      "LINESTRING (20 150, 100 150)",
      false);
  assertRelation(
      "ST_Touches",
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))",
      "MULTILINESTRING ((3 4, 6 4), (5 0, 5 4))",
      false);
  assertRelation(
      "ST_Touches", "POINT (1 2)", "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))", true);
  assertRelation(
      "ST_Touches",
      "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
      "POLYGON ((4 4, 4 5, 5 5, 5 4, 4 4))",
      false);
  assertRelation(
      "ST_Touches",
      "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
      "LINESTRING (0 0, 1 1)",
      true);
  assertRelation(
      "ST_Touches",
      "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
      "POLYGON ((3 3, 3 5, 5 5, 5 3, 3 3))",
      true);

  VELOX_ASSERT_USER_THROW(
      assertRelation(
          "ST_Touches",
          "MULTIPOLYGON ( ((0 0, 0 2, 2 2, 2 0, 0 0)), ((1 1, 1 3, 3 3, 3 1, 1 1)) )",
          "POINT (1 1)",
          false),
      "TopologyException: side location conflict at 1 2. This can occur if the input geometry is invalid.");
}

TEST_F(GeometryFunctionsTest, testStWithin) {
  // 0, 1: Within (1, 0: Contains)
  assertRelation(
      "ST_Within", kRelationGeometriesWKT[0], kRelationGeometriesWKT[1], true);
  // 2, 3: Contains
  assertRelation(
      "ST_Within", kRelationGeometriesWKT[3], kRelationGeometriesWKT[2], true);
  // 4, 5: Contains
  assertRelation(
      "ST_Within", kRelationGeometriesWKT[5], kRelationGeometriesWKT[4], true);
  // 1, 6: Contains
  assertRelation(
      "ST_Within", kRelationGeometriesWKT[6], kRelationGeometriesWKT[1], true);
  // 2, 6: Contains
  assertRelation(
      "ST_Within", kRelationGeometriesWKT[6], kRelationGeometriesWKT[2], true);
  // 2, 7: Contains
  assertRelation(
      "ST_Within", kRelationGeometriesWKT[7], kRelationGeometriesWKT[2], true);
  // 3, 6: Contains
  assertRelation(
      "ST_Within", kRelationGeometriesWKT[6], kRelationGeometriesWKT[3], true);
  // 3, 7: Contains
  assertRelation(
      "ST_Within", kRelationGeometriesWKT[7], kRelationGeometriesWKT[3], true);
  // 4, 7: Contains
  assertRelation(
      "ST_Within", kRelationGeometriesWKT[7], kRelationGeometriesWKT[4], true);

  assertRelation("ST_Within", std::nullopt, "POINT (150 150)", false);
  assertRelation("ST_Within", "POINT (50 100)", "POINT (150 150)", false);
  assertRelation(
      "ST_Within", "POINT (50 100)", "MULTIPOINT (50 100, 50 200)", true);
  assertRelation(
      "ST_Within",
      "LINESTRING (50 100, 50 200)",
      "LINESTRING (50 50, 50 250)",
      true);
  assertRelation(
      "ST_Within",
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))",
      "MULTILINESTRING ((3 4, 6 4), (5 0, 5 4))",
      false);
  assertRelation(
      "ST_Within", "POINT (3 2)", "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))", true);
  assertRelation(
      "ST_Within",
      "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
      "POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0))",
      true);
  assertRelation(
      "ST_Within",
      "LINESTRING (1 1, 3 3)",
      "POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0))",
      true);
  assertRelation(
      "ST_Within",
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((0 0, 0 2, 2 2, 2 0, 0 0)))",
      "POLYGON ((0 1, 3 1, 3 3, 0 3, 0 1))",
      false);
  assertRelation(
      "ST_Within",
      "POLYGON ((1 1, 1 5, 5 5, 5 1, 1 1))",
      "POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0))",
      false);

  VELOX_ASSERT_USER_THROW(
      assertRelation(
          "ST_Within",
          "POINT (0 0)",
          "MULTIPOLYGON ( ((0 0, 0 2, 2 2, 2 0, 0 0)), ((1 1, 1 3, 3 3, 3 1, 1 1)) )",
          false),
      "TopologyException: side location conflict at 1 2. This can occur if the input geometry is invalid.");
}

// Overlay operations

TEST_F(GeometryFunctionsTest, testStDifference) {
  assertOverlay("ST_Difference", std::nullopt, std::nullopt, std::nullopt);
  assertOverlay(
      "ST_Difference", "POINT (50 100)", "POINT (150 150)", "POINT (50 100)");
  assertOverlay(
      "ST_Difference",
      "MULTIPOINT (50 100, 50 200)",
      "POINT (50 100)",
      "POINT (50 200)");
  assertOverlay(
      "ST_Difference",
      "LINESTRING (50 100, 50 200)",
      "LINESTRING (50 50, 50 150)",
      "LINESTRING (50 150, 50 200)");
  assertOverlay(
      "ST_Difference",
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))",
      "MULTILINESTRING ((2 1, 4 1), (3 3, 7 3))",
      "MULTILINESTRING ((1 1, 2 1), (4 1, 5 1), (2 4, 4 4))");
  assertOverlay(
      "ST_Difference",
      "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))",
      "POLYGON ((2 2, 2 5, 5 5, 5 2, 2 2))",
      "POLYGON ((1 4, 2 4, 2 2, 4 2, 4 1, 1 1, 1 4))");
  assertOverlay(
      "ST_Difference",
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((0 0, 0 1, 1 1, 1 0, 0 0)))",
      "POLYGON ((0 1, 3 1, 3 3, 0 3, 0 1))",
      "POLYGON ((0 1, 1 1, 1 0, 0 0, 0 1))");

  ASSERT_THROW(
      assertOverlay(
          "ST_Difference",
          "LINESTRING (0 0, 1 1, 1 0, 0 1)",
          "MULTIPOLYGON ( ((0 0, 0 2, 2 2, 2 0, 0 0)), ((1 1, 1 3, 3 3, 3 1, 1 1)) )",
          "POINT EMPTY"),
      facebook::velox::VeloxUserError);
}

TEST_F(GeometryFunctionsTest, testStIntersection) {
  assertOverlay("ST_Intersection", std::nullopt, std::nullopt, std::nullopt);
  assertOverlay(
      "ST_Intersection", "POINT (50 100)", "POINT (150 150)", "POINT EMPTY");
  assertOverlay(
      "ST_Intersection",
      "MULTIPOINT (50 100, 50 200)",
      "POINT (50 100)",
      "POINT (50 100)");
  assertOverlay(
      "ST_Intersection",
      "LINESTRING (50 100, 50 200)",
      "LINESTRING (20 150, 100 150)",
      "POINT (50 150)");
  assertOverlay(
      "ST_Intersection",
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))",
      "MULTILINESTRING ((3 4, 6 4), (5 0, 5 4))",
      "GEOMETRYCOLLECTION (LINESTRING (3 4, 4 4), POINT (5 1))");
  assertOverlay(
      "ST_Intersection",
      "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
      "POLYGON ((4 4, 4 5, 5 5, 5 4, 4 4))",
      "POLYGON EMPTY");
  assertOverlay(
      "ST_Intersection",
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((0 0, 0 1, 1 1, 1 0, 0 0)))",
      "POLYGON ((0 1, 3 1, 3 3, 0 3, 0 1))",
      "GEOMETRYCOLLECTION (POLYGON ((1 3, 3 3, 3 1, 1 1, 1 3)), LINESTRING (0 1, 1 1))");
  assertOverlay(
      "ST_Intersection",
      "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))",
      "LINESTRING (2 0, 2 3)",
      "LINESTRING (2 1, 2 3)");
  assertOverlay(
      "ST_Intersection",
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))",
      "LINESTRING (0 0, 1 -1, 1 2)",
      "GEOMETRYCOLLECTION (LINESTRING (1 1, 1 0), POINT (0 0))");

  ASSERT_THROW(
      assertOverlay(
          "ST_Intersection",
          "LINESTRING (0 0, 1 1, 1 0, 0 1)",
          "MULTIPOLYGON ( ((0 0, 0 2, 2 2, 2 0, 0 0)), ((1 1, 1 3, 3 3, 3 1, 1 1)) )",
          "POINT EMPTY"),
      facebook::velox::VeloxUserError);
}

TEST_F(GeometryFunctionsTest, testStSymDifference) {
  assertOverlay("ST_SymDifference", std::nullopt, std::nullopt, std::nullopt);
  assertOverlay(
      "ST_SymDifference",
      "POINT (50 100)",
      "POINT (50 150)",
      "MULTIPOINT (50 100, 50 150)");
  assertOverlay(
      "ST_SymDifference",
      "MULTIPOINT (50 100, 60 200)",
      "MULTIPOINT (60 200, 70 150)",
      "MULTIPOINT (50 100, 70 150)");
  assertOverlay(
      "ST_SymDifference",
      "LINESTRING (50 100, 50 200)",
      "LINESTRING (50 50, 50 150)",
      "MULTILINESTRING ((50 150, 50 200), (50 50, 50 100))");
  assertOverlay(
      "ST_SymDifference",
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))",
      "MULTILINESTRING ((3 4, 6 4), (5 0, 5 4))",
      "MULTILINESTRING ((1 1, 5 1), (2 4, 3 4), (4 4, 5 4), (5 4, 6 4), (5 0, 5 1), (5 1, 5 4))");
  assertOverlay(
      "ST_SymDifference",
      "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))",
      "POLYGON ((2 2, 2 5, 5 5, 5 2, 2 2))",
      "MULTIPOLYGON (((1 4, 2 4, 2 2, 4 2, 4 1, 1 1, 1 4)), ((4 4, 2 4, 2 5, 5 5, 5 2, 4 2, 4 4)))");
  assertOverlay(
      "ST_SymDifference",
      "MULTIPOLYGON (((0 0, 0 2, 2 2, 2 0, 0 0)), ((2 2, 2 4, 4 4, 4 2, 2 2)))",
      "POLYGON ((0 0, 0 3, 3 3, 3 0, 0 0))",
      "MULTIPOLYGON (((0 2, 0 3, 2 3, 2 2, 0 2)), ((2 2, 3 2, 3 0, 2 0, 2 2)), ((2 4, 4 4, 4 2, 3 2, 3 3, 2 3, 2 4)))");

  ASSERT_THROW(
      assertOverlay(
          "ST_SymDifference",
          "LINESTRING (0 0, 1 1, 1 0, 0 1)",
          "MULTIPOLYGON ( ((0 0, 0 2, 2 2, 2 0, 0 0)), ((1 1, 1 3, 3 3, 3 1, 1 1)) )",
          "POINT EMPTY"),
      facebook::velox::VeloxUserError);
}

TEST_F(GeometryFunctionsTest, testStUnion) {
  std::array<std::string_view, 7> emptyWkts = {
      "POINT EMPTY",
      "MULTIPOINT EMPTY",
      "LINESTRING EMPTY",
      "MULTILINESTRING EMPTY",
      "POLYGON EMPTY",
      "MULTIPOLYGON EMPTY",
      "GEOMETRYCOLLECTION EMPTY"};
  std::array<std::string_view, 7> simpleWkts = {
      "POINT (1 2)",
      "MULTIPOINT (1 2, 3 4)",
      "LINESTRING (0 0, 2 2, 4 4)",
      "MULTILINESTRING ((0 0, 2 2, 4 4), (5 5, 7 7, 9 9))",
      "POLYGON ((0 1, 1 1, 1 0, 0 0, 0 1))",
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((2 4, 2 6, 6 6, 6 4, 2 4)))",
      "GEOMETRYCOLLECTION (LINESTRING (0 5, 5 5), POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1)))"};

  // empty geometry
  for (std::string_view emptyWkt : emptyWkts) {
    for (std::string_view simpleWkt : simpleWkts) {
      assertOverlay("ST_Union", emptyWkt, simpleWkt, simpleWkt);
    }
  }

  // self union
  for (std::string_view simpleWkt : simpleWkts) {
    assertOverlay("ST_Union", simpleWkt, simpleWkt, simpleWkt);
  }

  assertOverlay("ST_Union", std::nullopt, std::nullopt, std::nullopt);

  // touching union
  assertOverlay(
      "ST_Union",
      "POINT (1 2)",
      "MULTIPOINT (1 2, 3 4)",
      "MULTIPOINT (1 2, 3 4)");
  assertOverlay(
      "ST_Union",
      "MULTIPOINT (1 2)",
      "MULTIPOINT (1 2, 3 4)",
      "MULTIPOINT (1 2, 3 4)");
  assertOverlay(
      "ST_Union",
      "LINESTRING (0 1, 1 2)",
      "LINESTRING (1 2, 3 4)",
      "LINESTRING (0 1, 1 2, 3 4)");
  assertOverlay(
      "ST_Union",
      "MULTILINESTRING ((0 0, 2 2, 4 4), (5 5, 7 7, 9 9))",
      "MULTILINESTRING ((5 5, 7 7, 9 9), (11 11, 13 13, 15 15))",
      "MULTILINESTRING ((0 0, 2 2, 4 4), (5 5, 7 7, 9 9), (11 11, 13 13, 15 15))");
  assertOverlay(
      "ST_Union",
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))",
      "POLYGON ((1 0, 2 0, 2 1, 1 1, 1 0))",
      "POLYGON ((0 0, 0 1, 1 1, 2 1, 2 0, 1 0, 0 0))");
  assertOverlay(
      "ST_Union",
      "MULTIPOLYGON (((0 0, 0 1, 1 1, 1 0, 0 0)))",
      "MULTIPOLYGON (((1 0, 2 0, 2 1, 1 1, 1 0)))",
      "POLYGON ((0 0, 0 1, 1 1, 2 1, 2 0, 1 0, 0 0))");
  assertOverlay(
      "ST_Union",
      "GEOMETRYCOLLECTION (POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0)), POINT (1 2))",
      "GEOMETRYCOLLECTION (POLYGON ((1 0, 2 0, 2 1, 1 1, 1 0)), MULTIPOINT ((1 2), (3 4)))",
      "GEOMETRYCOLLECTION (POINT (1 2), POINT (3 4), POLYGON ((0 0, 0 1, 1 1, 2 1, 2 0, 1 0, 0 0)))");

  // within union
  assertOverlay(
      "ST_Union",
      "MULTIPOINT (20 20, 25 25)",
      "POINT (25 25)",
      "MULTIPOINT (20 20, 25 25)");
  assertOverlay(
      "ST_Union",
      "LINESTRING (20 20, 30 30)",
      "POINT (25 25)",
      "LINESTRING (20 20, 30 30)");
  assertOverlay(
      "ST_Union",
      "LINESTRING (20 20, 30 30)",
      "LINESTRING (25 25, 27 27)",
      "LINESTRING (20 20, 25 25, 27 27, 30 30)");
  assertOverlay(
      "ST_Union",
      "POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0))",
      "POLYGON ((1 1, 1 2, 2 2, 2 1, 1 1))",
      "POLYGON ((0 4, 4 4, 4 0, 0 0, 0 4))");
  assertOverlay(
      "ST_Union",
      "MULTIPOLYGON (((0 0, 0 2, 2 2, 2 0, 0 0)), ((2 2, 2 4, 4 4, 4 2, 2 2)))",
      "POLYGON ((2 2, 2 3, 3 3, 3 2, 2 2))",
      "MULTIPOLYGON (((2 2, 2 3, 2 4, 4 4, 4 2, 3 2, 2 2)), ((0 0, 0 2, 2 2, 2 0, 0 0)))");
  assertOverlay(
      "ST_Union",
      "GEOMETRYCOLLECTION (POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0)), MULTIPOINT (20 20, 25 25))",
      "GEOMETRYCOLLECTION (POLYGON ((1 1, 1 2, 2 2, 2 1, 1 1)), POINT (25 25))",
      "GEOMETRYCOLLECTION (MULTIPOINT (20 20, 25 25), POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0)))");

  // overlap union
  assertOverlay(
      "ST_Union",
      "LINESTRING (1 1, 3 1)",
      "LINESTRING (2 1, 4 1)",
      "LINESTRING (1 1, 2 1, 3 1, 4 1)");
  assertOverlay(
      "ST_Union",
      "MULTILINESTRING ((1 1, 3 1))",
      "MULTILINESTRING ((2 1, 4 1))",
      "LINESTRING (1 1, 2 1, 3 1, 4 1)");
  assertOverlay(
      "ST_Union",
      "POLYGON ((1 1, 3 1, 3 3, 1 3, 1 1))",
      "POLYGON ((2 2, 4 2, 4 4, 2 4, 2 2))",
      "POLYGON ((1 1, 1 3, 2 3, 2 4, 4 4, 4 2, 3 2, 3 1, 1 1))");
  assertOverlay(
      "ST_Union",
      "MULTIPOLYGON (((1 1, 3 1, 3 3, 1 3, 1 1)))",
      "MULTIPOLYGON (((2 2, 4 2, 4 4, 2 4, 2 2)))",
      "POLYGON ((1 1, 1 3, 2 3, 2 4, 4 4, 4 2, 3 2, 3 1, 1 1))");
  assertOverlay(
      "ST_Union",
      "GEOMETRYCOLLECTION (POLYGON ((1 1, 3 1, 3 3, 1 3, 1 1)), LINESTRING (1 1, 3 1))",
      "GEOMETRYCOLLECTION (POLYGON ((2 2, 4 2, 4 4, 2 4, 2 2)), LINESTRING (2 1, 4 1))",
      "GEOMETRYCOLLECTION (LINESTRING (3 1, 4 1), POLYGON ((1 1, 1 3, 2 3, 2 4, 4 4, 4 2, 3 2, 3 1, 2 1, 1 1)))");

  ASSERT_THROW(
      assertOverlay(
          "ST_Union",
          "LINESTRING (0 0, 1 1, 1 0, 0 1)",
          "MULTIPOLYGON ( ((0 0, 0 2, 2 2, 2 0, 0 0)), ((1 1, 1 3, 3 3, 3 1, 1 1)) )",
          "POINT EMPTY"),
      facebook::velox::VeloxUserError);
}

// Accessors

TEST_F(GeometryFunctionsTest, testStIsSimpleValid) {
  const auto assertStIsValidSimpleFunc = [&](std::optional<std::string> wkt,
                                             bool expectedValid,
                                             bool expectedSimple) {
    std::optional<bool> validResult =
        evaluateOnce<bool>("ST_IsValid(ST_GeometryFromText(c0))", wkt);
    std::optional<bool> simpleResult =
        evaluateOnce<bool>("ST_IsSimple(ST_GeometryFromText(c0))", wkt);

    if (wkt.has_value()) {
      ASSERT_TRUE(validResult.has_value());
      ASSERT_TRUE(simpleResult.has_value());
      ASSERT_EQ(validResult.value(), expectedValid)
          << " from WKT: " << wkt.value();
      ASSERT_EQ(simpleResult.value(), expectedSimple)
          << " from WKT: " << wkt.value();
    } else {
      ASSERT_FALSE(validResult.has_value());
      ASSERT_FALSE(simpleResult.has_value());
    }
  };

  assertStIsValidSimpleFunc(std::nullopt, true, true);
  assertStIsValidSimpleFunc("POINT EMPTY", true, true);
  assertStIsValidSimpleFunc("MULTIPOINT EMPTY", true, true);
  assertStIsValidSimpleFunc("LINESTRING EMPTY", true, true);
  assertStIsValidSimpleFunc("MULTILINESTRING EMPTY", true, true);
  assertStIsValidSimpleFunc("POLYGON EMPTY", true, true);
  assertStIsValidSimpleFunc("MULTIPOLYGON EMPTY", true, true);
  assertStIsValidSimpleFunc("GEOMETRYCOLLECTION EMPTY", true, true);

  // valid geometries
  assertStIsValidSimpleFunc("POINT (1.5 2.5)", true, true);

  assertStIsValidSimpleFunc("MULTIPOINT (1 2, 3 4)", true, true);
  assertStIsValidSimpleFunc("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", true, true);
  // Repeated point
  assertStIsValidSimpleFunc(
      "MULTIPOINT ((0 0), (0 1), (0 1), (1 1))", true, false);
  // Duplicate point
  assertStIsValidSimpleFunc("MULTIPOINT (1 2, 2 4, 3 6, 1 2)", true, false);

  assertStIsValidSimpleFunc("LINESTRING (0 0, 1 2, 3 4)", true, true);
  // Geos/JTS considers LineStrings with repeated points valid/simple (it drops
  // the dupes), even though ISO says they should be invalid.
  assertStIsValidSimpleFunc(
      "LINESTRING (0 0, 0 1, 0 1, 1 1, 1 0, 0 0)", true, true);
  // Valid but not simple: Self-intersection at (0, 1) (vertex)
  assertStIsValidSimpleFunc(
      "LINESTRING (0 0, -1 0.5, 0 1, 1 1, 1 0, 0 1, 0 0)", true, false);
  assertStIsValidSimpleFunc("LINESTRING (8 4, 5 7)", true, true);
  assertStIsValidSimpleFunc("LINESTRING (1 1, 2 2, 1 3, 1 1)", true, true);
  // Valid but not simple: Self-intersection at (0.5, 0.5) (in segment)
  assertStIsValidSimpleFunc("LINESTRING (0 0, 1 1, 1 0, 0 1)", true, false);

  assertStIsValidSimpleFunc(
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))", true, true);
  // Valid but not simple: Self-intersection at (2, 1)
  assertStIsValidSimpleFunc(
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 0))", true, false);

  assertStIsValidSimpleFunc("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))", true, true);
  // Hole outside of shell
  assertStIsValidSimpleFunc(
      "POLYGON ((0 0, 0 1, 0 1, 1 1, 1 0, 0 0), (2 2, 2 3, 3 3, 3 2, 2 2))",
      false,
      true);
  // Hole outside of shell
  assertStIsValidSimpleFunc(
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0), (2 2, 2 3, 3 3, 3 2, 2 2))",
      false,
      true);
  // Backtrack from (2, 1) to (1, 1)
  assertStIsValidSimpleFunc(
      "POLYGON ((0 0, 0 1, 2 1, 1 1, 1 0, 0 0))", false, false);
  // Hole segment (0 1, 1 1) overlaps shell segment
  assertStIsValidSimpleFunc(
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0), (0 1, 1 1, 0.5 0.5, 0 1))",
      false,
      true);
  // Hole intersects shell at two points (0, 0) and (1, 1)
  assertStIsValidSimpleFunc(
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0), (0 0, 0.5 0.7, 1 1, 0.5 0.4, 0 0))",
      false,
      true);
  // Shell intersects self at (0, 1)
  assertStIsValidSimpleFunc(
      "POLYGON ((0 0, -1 0.5, 0 1, 1 1, 1 0, 0 1, 0 0))", false, false);
  assertStIsValidSimpleFunc("POLYGON ((2 0, 2 1, 3 1, 2 0))", true, true);

  assertStIsValidSimpleFunc(
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((2 4, 2 6, 6 6, 6 4, 2 4)))",
      true,
      true);
  // Overlapping rectangles.  This is invalid but simple because multipolygon
  // rules are weird.
  assertStIsValidSimpleFunc(
      "MULTIPOLYGON (((0 0, 0 1, 1 1, 1 0, 0 0)), ((0.5 0.5, 0.5 2, 2 2, 2 0.5, 0.5 0.5)))",
      false,
      true);

  assertStIsValidSimpleFunc(
      "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (0 0, 1 2, 3 4), POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))) ",
      true,
      true);
  // Invalid Polygon
  assertStIsValidSimpleFunc(
      "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 0 1, 2 1, 1 1, 1 0, 0 0)))",
      false,
      false);
}

TEST_F(GeometryFunctionsTest, testStArea) {
  const auto testStAreaFunc = [&](std::optional<std::string> wkt,
                                  std::optional<double> expectedArea) {
    std::optional<double> result =
        evaluateOnce<double>("ST_Area(ST_GeometryFromText(c0))", wkt);

    if (wkt.has_value()) {
      ASSERT_TRUE(result.has_value());
      ASSERT_TRUE(expectedArea.has_value());
      ASSERT_EQ(result.value(), expectedArea.value());
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  testStAreaFunc("POLYGON ((2 2, 2 6, 6 6, 6 2, 2 2))", 16.0);
  testStAreaFunc("POLYGON EMPTY", 0.0);
  testStAreaFunc("LINESTRING (1 4, 2 5)", 0.0);
  testStAreaFunc("LINESTRING EMPTY", 0.0);
  testStAreaFunc("POINT (1 4)", 0.0);
  testStAreaFunc("POINT EMPTY", 0.0);
  testStAreaFunc("GEOMETRYCOLLECTION EMPTY", 0.0);

  // Test basic geometry collection. Area is the area of the polygon.
  testStAreaFunc(
      "GEOMETRYCOLLECTION (POINT (8 8), LINESTRING (5 5, 6 6), POLYGON ((1 1, 3 1, 3 4, 1 4, 1 1)))",
      6.0);

  // Test overlapping geometries. Area is the sum of the individual elements
  testStAreaFunc(
      "GEOMETRYCOLLECTION (POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0)), POLYGON ((1 1, 3 1, 3 3, 1 3, 1 1)))",
      8.0);

  // Test nested geometry collection
  testStAreaFunc(
      "GEOMETRYCOLLECTION (POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0)), POLYGON ((1 1, 3 1, 3 3, 1 3, 1 1)), GEOMETRYCOLLECTION (POINT (8 8), LINESTRING (5 5, 6 6), POLYGON ((1 1, 3 1, 3 4, 1 4, 1 1))))",
      14.0);
}

TEST_F(GeometryFunctionsTest, testGeometryInvalidReason) {
  const auto assertInvalidReason =
      [&](const std::optional<std::string>& wkt,
          const std::optional<std::string>& expectedMessage) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "geometry_invalid_reason(ST_GeometryFromText(c0))", wkt);

        if (wkt.has_value() && expectedMessage.has_value()) {
          ASSERT_TRUE(result.has_value()) << " from WKT: " << wkt.value();
          ASSERT_EQ(result.value(), expectedMessage.value())
              << " from WKT: " << wkt.value();
        } else {
          ASSERT_FALSE(result.has_value()) << " from WKT: " << wkt.value();
        }
      };

  // Invalid geometries
  assertInvalidReason(
      "POLYGON ((0 0, 0 1, 0 1, 1 1, 1 0, 0 0), (2 2, 2 3, 3 3, 3 2, 2 2))",
      "Invalid Polygon: Hole lies outside shell");
  assertInvalidReason(
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0), (2 2, 2 3, 3 3, 3 2, 2 2))",
      "Invalid Polygon: Hole lies outside shell");
  assertInvalidReason(
      "POLYGON ((0 0, 0 1, 2 1, 1 1, 1 0, 0 0))",
      "Invalid Polygon: Ring Self-intersection");
  assertInvalidReason(
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0), (0 1, 1 1, 0.5 0.5, 0 1))",
      "Invalid Polygon: Self-intersection");
  assertInvalidReason(
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0), (0 0, 0.5 0.7, 1 1, 0.5 0.4, 0 0))",
      "Invalid Polygon: Interior is disconnected");
  assertInvalidReason(
      "POLYGON ((0 0, -1 0.5, 0 1, 1 1, 1 0, 0 1, 0 0))",
      "Invalid Polygon: Ring Self-intersection");
  assertInvalidReason(
      "MULTIPOLYGON (((0 0, 0 1, 1 1, 1 0, 0 0)), ((0.5 0.5, 0.5 2, 2 2, 2 0.5, 0.5 0.5)))",
      "Invalid MultiPolygon: Self-intersection");
  assertInvalidReason(
      "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 0 1, 2 1, 1 1, 1 0, 0 0)))",
      "Invalid GeometryCollection: Ring Self-intersection");

  // non-simple geometries
  assertInvalidReason(
      "LINESTRING (0 0, -1 0.5, 0 1, 1 1, 1 0, 0 1, 0 0)",
      "Non-simple LineString: Self-intersection at or near (0 1)");
  assertInvalidReason(
      "MULTIPOINT (1 2, 2 4, 3 6, 1 2)",
      "Non-simple MultiPoint: Repeated point (1 2)");
  assertInvalidReason(
      "LINESTRING (0 0, 1 1, 1 0, 0 1)",
      "Non-simple LineString: Self-intersection at or near (0.5 0.5)");
  assertInvalidReason(
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 0))",
      "Non-simple MultiLineString: Self-intersection at or near (3.5 1)");

  // valid geometries
  assertInvalidReason(std::nullopt, std::nullopt);
  assertInvalidReason("LINESTRING EMPTY", std::nullopt);
  assertInvalidReason("POINT (1 2)", std::nullopt);
  assertInvalidReason("POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))", std::nullopt);
  assertInvalidReason(
      "GEOMETRYCOLLECTION (MULTIPOINT (1 0, 1 1, 0 1, 0 0))", std::nullopt);
}

TEST_F(GeometryFunctionsTest, testSimplifyGeometry) {
  const auto assertSimplifyGeometry = [&](const std::optional<std::string>& wkt,
                                          std::optional<double> tolerance,
                                          const std::optional<std::string>&
                                              expectedWkt) {
    std::optional<bool> result = evaluateOnce<bool>(
        "ST_Equals(simplify_geometry(ST_GeometryFromText(c0), c1), ST_GeometryFromText(c2))",
        wkt,
        tolerance,
        expectedWkt);

    if (wkt.has_value() && tolerance.has_value() && expectedWkt.has_value()) {
      ASSERT_TRUE(result.has_value());
      ASSERT_TRUE(result.value()) << " from WKT: " << wkt.value();
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  assertSimplifyGeometry("POLYGON EMPTY", 1.0, "POLYGON EMPTY");
  assertSimplifyGeometry(
      "POLYGON ((1 0, 2 1, 3 1, 3 1, 4 1, 1 0))",
      1.5,
      "POLYGON ((1 0, 2 1, 4 1, 1 0))");
  // Simplifying by 0.0 leaves the geometry unchanged
  assertSimplifyGeometry(
      "POLYGON ((1 0, 2 1, 3 1, 3 1, 4 1, 1 0))",
      0.0,
      "POLYGON ((1 0, 2 1, 3 1, 3 1, 4 1, 1 0))");

  // Check different tolerance produce different answers
  assertSimplifyGeometry(
      "POLYGON ((1 0, 1 1, 2 1, 2 3, 3 3, 3 1, 4 1, 4 0, 1 0))",
      1.0,
      "POLYGON ((1 0, 2 3, 3 3, 4 0, 1 0))");
  assertSimplifyGeometry(
      "POLYGON ((1 0, 1 1, 2 1, 2 3, 3 3, 3 1, 4 1, 4 0, 1 0))",
      0.5,
      "POLYGON ((1 0, 1 1, 2 1, 2 3, 3 3, 3 1, 4 1, 4 0, 1 0))");

  assertSimplifyGeometry(
      "POLYGON ((1 0, 2 1, 3 1, 3 1, 4 1, 1 0))",
      std::nullopt,
      "POLYGON ((1 0, 2 1, 4 1, 1 0))");
  assertSimplifyGeometry(std::nullopt, 1.0, "POLYGON ((1 0, 2 1, 4 1, 1 0))");

  VELOX_ASSERT_USER_THROW(
      assertSimplifyGeometry(
          "POLYGON ((1 0, 1 1, 2 1, 2 3, 3 3, 3 1, 4 1, 4 0, 1 0))",
          -0.5,
          "POLYGON ((1 0, 1 1, 2 1, 2 3, 3 3, 3 1, 4 1, 4 0, 1 0))"),
      "simplification tolerance must be a non-negative finite number");

  VELOX_ASSERT_USER_THROW(
      assertSimplifyGeometry(
          "POLYGON ((1 0, 1 1, 2 1, 2 3, 3 3, 3 1, 4 1, 4 0, 1 0))",
          std::nan("1"),
          "POLYGON ((1 0, 1 1, 2 1, 2 3, 3 3, 3 1, 4 1, 4 0, 1 0))"),
      "simplification tolerance must be a non-negative finite number");
}

TEST_F(GeometryFunctionsTest, testStBoundary) {
  const auto testStBoundaryFunc = [&](const std::optional<std::string>& wkt,
                                      const std::optional<std::string>&
                                          expected) {
    std::optional<bool> result = evaluateOnce<bool>(
        "ST_Equals(ST_Boundary(ST_GeometryFromText(c0)), ST_GeometryFromText(c1))",
        wkt,
        expected);

    if (wkt.has_value()) {
      ASSERT_TRUE(result.has_value());
      ASSERT_TRUE(expected.has_value());
      ASSERT_TRUE(result.value());
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  testStBoundaryFunc("POINT (1 2)", "GEOMETRYCOLLECTION EMPTY");
  testStBoundaryFunc(
      "MULTIPOINT (1 2, 2 4, 3 6, 4 8)", "GEOMETRYCOLLECTION EMPTY");
  testStBoundaryFunc("LINESTRING EMPTY", "MULTIPOINT EMPTY");
  testStBoundaryFunc("LINESTRING (8 4, 5 7)", "MULTIPOINT (8 4, 5 7)");
  testStBoundaryFunc(
      "LINESTRING (100 150, 50 60, 70 80, 160 170)",
      "MULTIPOINT (100 150, 160 170)");
  testStBoundaryFunc(
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))",
      "MULTIPOINT (1 1, 2 4, 4 4, 5 1)");
  testStBoundaryFunc(
      "POLYGON ((1 1, 4 1, 1 4, 1 1))", "LINESTRING (1 1, 1 4, 4 1, 1 1)");
  testStBoundaryFunc(
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((0 0, 0 2, 2 2, 2 0, 0 0)))",
      "MULTILINESTRING ((1 1, 1 3, 3 3, 3 1, 1 1), (0 0, 0 2, 2 2, 2 0, 0 0))");
}

TEST_F(GeometryFunctionsTest, testStCentroid) {
  const auto testStCentroidFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "ST_AsText(ST_Centroid(ST_GeometryFromText(c0)))", wkt);

        if (wkt.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_TRUE(expected.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  testStCentroidFunc("LINESTRING EMPTY", "POINT EMPTY");
  testStCentroidFunc("POINT (3 5)", "POINT (3 5)");
  testStCentroidFunc("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", "POINT (2.5 5)");
  testStCentroidFunc("LINESTRING (1 1, 2 2, 3 3)", "POINT (2 2)");
  testStCentroidFunc("MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))", "POINT (3 2)");
  testStCentroidFunc("POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))", "POINT (2.5 2.5)");
  testStCentroidFunc("POLYGON ((1 1, 5 1, 3 4, 1 1))", "POINT (3 2)");
  testStCentroidFunc(
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((2 4, 2 6, 6 6, 6 4, 2 4)))",
      "POINT (3.3333333333333335 4)");
  testStCentroidFunc(
      "POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1))",
      "POINT (2.5416666666666665 2.5416666666666665)");

  VELOX_ASSERT_USER_THROW(
      testStCentroidFunc(
          "GEOMETRYCOLLECTION (POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0)), POLYGON ((1 1, 3 1, 3 3, 1 3, 1 1)), GEOMETRYCOLLECTION (POINT (8 8), LINESTRING (5 5, 6 6), POLYGON ((1 1, 3 1, 3 4, 1 4, 1 1))))",
          std::nullopt),
      "ST_Centroid only applies to Point or MultiPoint or LineString or MultiLineString or Polygon or MultiPolygon. Input type is: GeometryCollection");
}

TEST_F(GeometryFunctionsTest, testSTMin) {
  const auto assertPointMin = [&](const std::optional<std::string>& wkt,
                                  const std::optional<double> expectedXMin,
                                  const std::optional<double> expectedYMin) {
    std::optional<double> xMin =
        evaluateOnce<double>("ST_XMin(ST_GeometryFromText(c0))", wkt);
    std::optional<double> yMin =
        evaluateOnce<double>("ST_YMin(ST_GeometryFromText(c0))", wkt);
    if (expectedXMin.has_value() && expectedYMin.has_value()) {
      EXPECT_TRUE(xMin.has_value());
      EXPECT_TRUE(yMin.has_value());
      EXPECT_EQ(expectedXMin.value(), xMin.value());
      EXPECT_EQ(expectedYMin.value(), yMin.value());
    } else {
      EXPECT_FALSE(xMin.has_value());
      EXPECT_FALSE(yMin.has_value());
    }
  };

  assertPointMin("POINT (1.5 2.5)", 1.5, 2.5);
  assertPointMin("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", 1.0, 2.0);
  assertPointMin("LINESTRING (8 4, 5 7)", 5.0, 4.0);
  assertPointMin("MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))", 1.0, 1.0);
  assertPointMin("POLYGON ((2 0, 2 1, 3 1, 2 0))", 2.0, 0.0);
  assertPointMin(
      "MULTIPOLYGON (((1 10, 1 3, 3 3, 3 10, 1 10)), ((2 4, 2 6, 6 6, 6 4, 2 4)))",
      1.0,
      3.0);
  assertPointMin(
      "GEOMETRYCOLLECTION (POINT (5 1), LINESTRING (3 4, 4 4))", 3.0, 1.0);
  assertPointMin(std::nullopt, std::nullopt, std::nullopt);
  assertPointMin("POLYGON EMPTY", std::nullopt, std::nullopt);
}

TEST_F(GeometryFunctionsTest, testSTMax) {
  const auto assertPointMax = [&](const std::optional<std::string>& wkt,
                                  const std::optional<double> expectedXMax,
                                  const std::optional<double> expectedYMax) {
    std::optional<double> xMax =
        evaluateOnce<double>("ST_XMax(ST_GeometryFromText(c0))", wkt);
    std::optional<double> yMax =
        evaluateOnce<double>("ST_YMax(ST_GeometryFromText(c0))", wkt);
    if (expectedXMax.has_value() && expectedYMax.has_value()) {
      EXPECT_TRUE(xMax.has_value());
      EXPECT_TRUE(yMax.has_value());
      EXPECT_EQ(expectedXMax.value(), xMax.value());
      EXPECT_EQ(expectedYMax.value(), yMax.value());
    } else {
      EXPECT_FALSE(xMax.has_value());
      EXPECT_FALSE(yMax.has_value());
    }
  };

  assertPointMax("POINT (1.5 2.5)", 1.5, 2.5);
  assertPointMax("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", 4.0, 8.0);
  assertPointMax("LINESTRING (8 4, 5 7)", 8.0, 7.0);
  assertPointMax("MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))", 5.0, 4.0);
  assertPointMax("POLYGON ((2 0, 2 1, 3 1, 2 0))", 3.0, 1.0);
  assertPointMax(
      "MULTIPOLYGON (((1 10, 1 3, 3 3, 3 10, 1 10)), ((2 4, 2 6, 6 6, 6 4, 2 4)))",
      6.0,
      10.0);
  assertPointMax(
      "GEOMETRYCOLLECTION (POINT (5 1), LINESTRING (3 4, 4 4))", 5.0, 4.0);
  assertPointMax(std::nullopt, std::nullopt, std::nullopt);
  assertPointMax("POLYGON EMPTY", std::nullopt, std::nullopt);
}

TEST_F(GeometryFunctionsTest, testStGeometryType) {
  const auto testStGeometryTypeFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "ST_GeometryType(ST_GeometryFromText(c0))", wkt);

        if (wkt.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_TRUE(expected.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  testStGeometryTypeFunc("POINT EMPTY", "ST_Point");
  testStGeometryTypeFunc("POINT (3 5)", "ST_Point");
  testStGeometryTypeFunc("LINESTRING EMPTY", "ST_LineString");
  testStGeometryTypeFunc("LINESTRING (1 1, 2 2, 3 3)", "ST_LineString");
  testStGeometryTypeFunc("LINEARRING EMPTY", "ST_LineString");
  testStGeometryTypeFunc("POLYGON EMPTY", "ST_Polygon");
  testStGeometryTypeFunc("POLYGON ((1 1, 4 1, 1 4, 1 1))", "ST_Polygon");
  testStGeometryTypeFunc("MULTIPOINT EMPTY", "ST_MultiPoint");
  testStGeometryTypeFunc("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", "ST_MultiPoint");
  testStGeometryTypeFunc("MULTILINESTRING EMPTY", "ST_MultiLineString");
  testStGeometryTypeFunc(
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))", "ST_MultiLineString");
  testStGeometryTypeFunc("MULTIPOLYGON EMPTY", "ST_MultiPolygon");
  testStGeometryTypeFunc(
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((2 4, 2 6, 6 6, 6 4, 2 4)))",
      "ST_MultiPolygon");
  testStGeometryTypeFunc("GEOMETRYCOLLECTION EMPTY", "ST_GeomCollection");
  testStGeometryTypeFunc(
      "GEOMETRYCOLLECTION (POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0)), POLYGON ((1 1, 3 1, 3 3, 1 3, 1 1)), GEOMETRYCOLLECTION (POINT (8 8), LINESTRING (5 5, 6 6), POLYGON ((1 1, 3 1, 3 4, 1 4, 1 1))))",
      "ST_GeomCollection");
}

TEST_F(GeometryFunctionsTest, testStDistance) {
  const auto testStDistanceFunc = [&](const std::optional<std::string>& wkt1,
                                      const std::optional<std::string>& wkt2,
                                      const std::optional<double>& expected =
                                          std::nullopt) {
    std::optional<double> result = evaluateOnce<double>(
        "ST_Distance(ST_GeometryFromText(c0), ST_GeometryFromText(c1))",
        wkt1,
        wkt2);

    if (wkt1.has_value() && wkt2.has_value()) {
      if (expected.has_value()) {
        ASSERT_TRUE(result.has_value());
        ASSERT_EQ(result.value(), expected.value());
      } else {
        ASSERT_FALSE(result.has_value());
      }
    } else {
      ASSERT_FALSE(expected.has_value());
      ASSERT_FALSE(result.has_value());
    }
  };

  testStDistanceFunc("POINT (50 100)", "POINT (150 150)", 111.80339887498948);
  testStDistanceFunc("MULTIPOINT (50 100, 50 200)", "POINT (50 100)", 0.0);
  testStDistanceFunc(
      "LINESTRING (50 100, 50 200)",
      "LINESTRING (10 10, 20 20)",
      85.44003745317531);
  testStDistanceFunc(
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))",
      "LINESTRING (10 20, 20 50)'))",
      17.08800749063506);
  testStDistanceFunc(
      "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
      "POLYGON ((4 4, 4 5, 5 5, 5 4, 4 4))",
      1.4142135623730951);
  testStDistanceFunc(
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((0 0, 0 2, 2 2, 2 0, 0 0)))",
      "POLYGON ((10 100, 30 10, 30 100, 10 100))",
      27.892651361962706);

  testStDistanceFunc("POINT EMPTY", "POINT (150 150)");
  testStDistanceFunc("MULTIPOINT EMPTY", "POINT (50 100)");
  testStDistanceFunc("LINESTRING EMPTY", "LINESTRING (10 10, 20 20)");
  testStDistanceFunc("MULTILINESTRING EMPTY", "LINESTRING (10 20, 20 50)'))");
  testStDistanceFunc("POLYGON EMPTY", "POLYGON ((4 4, 4 5, 5 5, 5 4, 4 4))");
  testStDistanceFunc(
      "MULTIPOLYGON EMPTY", "POLYGON ((10 100, 30 10, 30 100, 10 100))");
  testStDistanceFunc(std::nullopt, "POINT (50 100)");
}

TEST_F(GeometryFunctionsTest, testStXY) {
  const auto testStX = [&](const std::optional<std::string>& wkt,
                           const std::optional<double>& expectedX =
                               std::nullopt) {
    std::optional<double> resultX =
        evaluateOnce<double>("ST_X(ST_GeometryFromText(c0))", wkt);

    if (expectedX.has_value()) {
      ASSERT_TRUE(resultX.has_value());
      ASSERT_EQ(expectedX.value(), resultX.value());
    } else {
      ASSERT_FALSE(resultX.has_value());
    }
  };
  const auto testStY = [&](const std::optional<std::string>& wkt,
                           const std::optional<double>& expectedY =
                               std::nullopt) {
    std::optional<double> resultY =
        evaluateOnce<double>("ST_Y(ST_GeometryFromText(c0))", wkt);

    if (expectedY.has_value()) {
      ASSERT_TRUE(resultY.has_value());
      ASSERT_EQ(expectedY.value(), resultY.value());
    } else {
      ASSERT_FALSE(resultY.has_value());
    }
  };

  testStX("POINT (1 2)", 1.0);
  testStY("POINT (1 2)", 2.0);
  testStX("POINT EMPTY", std::nullopt);
  testStY("POINT EMPTY", std::nullopt);
  VELOX_ASSERT_USER_THROW(
      testStX("GEOMETRYCOLLECTION EMPTY"),
      "ST_X requires a Point geometry, found GeometryCollection");
  VELOX_ASSERT_USER_THROW(
      testStY("GEOMETRYCOLLECTION EMPTY"),
      "ST_Y requires a Point geometry, found GeometryCollection");
  VELOX_ASSERT_USER_THROW(
      testStX("POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))"),
      "ST_X requires a Point geometry, found Polygon");
  VELOX_ASSERT_USER_THROW(
      testStY("POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))"),
      "ST_Y requires a Point geometry, found Polygon");
}

TEST_F(GeometryFunctionsTest, testStPolygon) {
  const auto testStPolygonFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result =
            evaluateOnce<std::string>("ST_AsText(ST_Polygon(c0))", wkt);

        if (wkt.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  testStPolygonFunc("POLYGON EMPTY", "POLYGON EMPTY");
  testStPolygonFunc(
      "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))'))",
      "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))");

  VELOX_ASSERT_USER_THROW(
      testStPolygonFunc("LINESTRING (1 1, 2 2, 1 3)", std::nullopt),
      "ST_Polygon only applies to Polygon. Input type is: LineString");

  VELOX_ASSERT_USER_THROW(
      testStPolygonFunc("POLYGON((-1 1, 1 -1))", std::nullopt),
      "Failed to parse WKT: IllegalArgumentException: Points of LinearRing do not form a closed linestring");
}

TEST_F(GeometryFunctionsTest, testStIsClosed) {
  const auto testStIsClosedFunc = [&](const std::optional<std::string>& wkt,
                                      const std::optional<bool>& expected) {
    std::optional<bool> result =
        evaluateOnce<bool>("ST_IsClosed(ST_GeometryFromText(c0))", wkt);

    if (wkt.has_value()) {
      ASSERT_TRUE(result.has_value());
      ASSERT_EQ(result.value(), expected.value());
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  testStIsClosedFunc("LINESTRING (1 1, 2 2, 1 3, 1 1)", true);
  testStIsClosedFunc("LINESTRING (1 1, 2 2, 1 3)", false);
  testStIsClosedFunc(
      "MULTILINESTRING ((1 1, 2 2, 1 3, 1 1), (4 4, 5 5))", false);
  testStIsClosedFunc(
      "MULTILINESTRING ((1 1, 2 2, 1 3, 1 1), (4 4, 5 4, 5 5, 4 5, 4 4))",
      true);

  VELOX_ASSERT_USER_THROW(
      testStIsClosedFunc("POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))", std::nullopt),
      "ST_IsClosed only applies to LineString or MultiLineString. Input type is: Polygon");
}

TEST_F(GeometryFunctionsTest, testStIsEmpty) {
  const auto testStIsClosedFunc = [&](const std::optional<std::string>& wkt,
                                      const std::optional<bool>& expected) {
    std::optional<bool> result =
        evaluateOnce<bool>("ST_IsEmpty(ST_GeometryFromText(c0))", wkt);

    if (wkt.has_value()) {
      ASSERT_TRUE(result.has_value());
      ASSERT_EQ(result.value(), expected.value());
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  testStIsClosedFunc("POINT (1.5 2.5)", false);
  testStIsClosedFunc("POLYGON EMPTY", true);
}

TEST_F(GeometryFunctionsTest, testStIsRing) {
  const auto testStIsRingFunc = [&](const std::optional<std::string>& wkt,
                                    const std::optional<bool>& expected) {
    std::optional<bool> result =
        evaluateOnce<bool>("ST_IsRing(ST_GeometryFromText(c0))", wkt);

    if (wkt.has_value()) {
      ASSERT_TRUE(result.has_value());
      ASSERT_EQ(result.value(), expected.value());
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  testStIsRingFunc("LINESTRING (8 4, 4 8)", false);
  testStIsRingFunc("LINESTRING (0 0, 1 1, 0 2, 0 0)", true);

  VELOX_ASSERT_USER_THROW(
      testStIsRingFunc("POLYGON ((2 0, 2 1, 3 1, 2 0))", true),
      "ST_IsRing only applies to LineString. Input type is: Polygon");
}

TEST_F(GeometryFunctionsTest, testStLength) {
  const auto testStLengthFunc = [&](const std::optional<std::string>& wkt,
                                    const std::optional<double>& expected) {
    std::optional<double> result =
        evaluateOnce<double>("ST_Length(ST_GeometryFromText(c0))", wkt);

    if (wkt.has_value()) {
      ASSERT_TRUE(result.has_value());
      ASSERT_EQ(result.value(), expected.value());
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  testStLengthFunc("LINESTRING EMPTY", 0.0);
  testStLengthFunc("LINESTRING (0 0, 2 2)", 2.8284271247461903);
  testStLengthFunc("MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))", 6.0);

  VELOX_ASSERT_USER_THROW(
      testStLengthFunc("POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))", std::nullopt),
      "ST_Length only applies to LineString or MultiLineString. Input type is: Polygon");
}

TEST_F(GeometryFunctionsTest, testStPointN) {
  const auto testStPointNFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<int32_t>& index,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "ST_AsText(ST_PointN(ST_GeometryFromText(c0), c1))", wkt, index);

        if (expected.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  testStPointNFunc("LINESTRING(1 2, 3 4, 5 6, 7 8)", 1, "POINT (1 2)");
  testStPointNFunc("LINESTRING(1 2, 3 4, 5 6, 7 8)", 3, "POINT (5 6)");
  testStPointNFunc("LINESTRING(1 2, 3 4, 5 6, 7 8)", 10, std::nullopt);
  testStPointNFunc("LINESTRING(1 2, 3 4, 5 6, 7 8)", 0, std::nullopt);
  testStPointNFunc("LINESTRING(1 2, 3 4, 5 6, 7 8)", -1, std::nullopt);

  VELOX_ASSERT_USER_THROW(
      testStPointNFunc("POINT (1 2)", -1, std::nullopt),
      "ST_PointN only applies to LineString. Input type is: Point");
}

TEST_F(GeometryFunctionsTest, testStStartPoint) {
  const auto testStStartPointFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "ST_AsText(ST_StartPoint(ST_GeometryFromText(c0)))", wkt);

        if (expected.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  testStStartPointFunc("LINESTRING (8 4, 4 8, 5 6)", "POINT (8 4)");
  testStStartPointFunc("LINESTRING (8 2, 4 12, 0 0)", "POINT (8 2)");
  testStStartPointFunc("LINESTRING (0 0, 4 12, 2 2)", "POINT (0 0)");
  testStStartPointFunc("LINESTRING EMPTY", std::nullopt);

  VELOX_ASSERT_USER_THROW(
      testStStartPointFunc("POINT (1 2)", std::nullopt),
      "ST_StartPoint only applies to LineString. Input type is: Point");
}

TEST_F(GeometryFunctionsTest, testStEndPoint) {
  const auto testStEndPointFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "ST_AsText(ST_EndPoint(ST_GeometryFromText(c0)))", wkt);

        if (expected.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  testStEndPointFunc("LINESTRING (8 4, 4 8, 5 6)", "POINT (5 6)");
  testStEndPointFunc("LINESTRING (8 2, 4 12, 0 0)", "POINT (0 0)");
  testStEndPointFunc("LINESTRING (0 0, 4 12, 2 2)", "POINT (2 2)");
  testStEndPointFunc("LINESTRING EMPTY", std::nullopt);

  VELOX_ASSERT_USER_THROW(
      testStEndPointFunc("POINT (1 2)", std::nullopt),
      "ST_EndPoint only applies to LineString. Input type is: Point");
}

TEST_F(GeometryFunctionsTest, testStGeometryN) {
  const auto testStGeometryNFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<int32_t>& index,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "ST_AsText(ST_GeometryN(ST_GeometryFromText(c0), c1))", wkt, index);

        if (expected.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  testStGeometryNFunc("POINT EMPTY", 1, std::nullopt);
  testStGeometryNFunc("LINESTRING EMPTY", 1, std::nullopt);
  testStGeometryNFunc(
      "LINESTRING(77.29 29.07, 77.42 29.26, 77.27 29.31, 77.29 29.07)",
      1,
      "LINESTRING (77.29 29.07, 77.42 29.26, 77.27 29.31, 77.29 29.07)");

  testStGeometryNFunc(
      "LINESTRING(77.29 29.07, 77.42 29.26, 77.27 29.31, 77.29 29.07)",
      2,
      std::nullopt);
  testStGeometryNFunc(
      "LINESTRING(77.29 29.07, 77.42 29.26, 77.27 29.31, 77.29 29.07)",
      -1,
      std::nullopt);
  testStGeometryNFunc(
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))",
      1,
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))");
  testStGeometryNFunc("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))", 2, std::nullopt);
  testStGeometryNFunc("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))", -1, std::nullopt);
  testStGeometryNFunc("POLYGON EMPTY", 0, std::nullopt);
  testStGeometryNFunc("POLYGON EMPTY", 2, std::nullopt);
  testStGeometryNFunc("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", 1, "POINT (1 2)");
  testStGeometryNFunc("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", 2, "POINT (2 4)");
  testStGeometryNFunc("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", 0, std::nullopt);
  testStGeometryNFunc("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", 5, std::nullopt);
  testStGeometryNFunc("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", -1, std::nullopt);
  testStGeometryNFunc(
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))", 1, "LINESTRING (1 1, 5 1)");
  testStGeometryNFunc(
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))", 2, "LINESTRING (2 4, 4 4)");
  testStGeometryNFunc(
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))", 0, std::nullopt);
  testStGeometryNFunc(
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))", 3, std::nullopt);
  testStGeometryNFunc(
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))", -1, std::nullopt);
  testStGeometryNFunc(
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((2 4, 2 6, 6 6, 6 4, 2 4)))",
      1,
      "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))");
  testStGeometryNFunc(
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((2 4, 2 6, 6 6, 6 4, 2 4)))",
      2,
      "POLYGON ((2 4, 2 6, 6 6, 6 4, 2 4))");
  testStGeometryNFunc(
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((2 4, 2 6, 6 6, 6 4, 2 4)))",
      0,
      std::nullopt);
  testStGeometryNFunc(
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((2 4, 2 6, 6 6, 6 4, 2 4)))",
      3,
      std::nullopt);
  testStGeometryNFunc(
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((2 4, 2 6, 6 6, 6 4, 2 4)))",
      -1,
      std::nullopt);
  testStGeometryNFunc(
      "GEOMETRYCOLLECTION(POINT(2 3), LINESTRING (2 3, 3 4))",
      1,
      "POINT (2 3)");
  testStGeometryNFunc(
      "GEOMETRYCOLLECTION(POINT(2 3), LINESTRING (2 3, 3 4))",
      2,
      "LINESTRING (2 3, 3 4)");
  testStGeometryNFunc(
      "GEOMETRYCOLLECTION(POINT(2 3), LINESTRING (2 3, 3 4))", 3, std::nullopt);
  testStGeometryNFunc(
      "GEOMETRYCOLLECTION(POINT(2 3), LINESTRING (2 3, 3 4))", 0, std::nullopt);
  testStGeometryNFunc(
      "GEOMETRYCOLLECTION(POINT(2 3), LINESTRING (2 3, 3 4))",
      -1,
      std::nullopt);
}

TEST_F(GeometryFunctionsTest, testStInteriorRingN) {
  const auto testStInteriorRingNFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<int32_t>& index,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "ST_AsText(ST_InteriorRingN(ST_GeometryFromText(c0), c1))",
            wkt,
            index);

        if (expected.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  testStInteriorRingNFunc(
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))", 1, std::nullopt);

  testStInteriorRingNFunc(
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))", 2, std::nullopt);
  testStInteriorRingNFunc(
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))", -1, std::nullopt);
  testStInteriorRingNFunc(
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))", 0, std::nullopt);
  testStInteriorRingNFunc("POLYGON EMPTY", 1, std::nullopt);
  testStInteriorRingNFunc(
      "POLYGON ((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))",
      1,
      "LINESTRING (1 1, 2 1, 2 2, 1 2, 1 1)");
  testStInteriorRingNFunc(
      "POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1), (3 3, 4 3, 4 4, 3 4, 3 3))",
      2,
      "LINESTRING (3 3, 4 3, 4 4, 3 4, 3 3)");

  VELOX_ASSERT_USER_THROW(
      testStInteriorRingNFunc("POINT EMPTY", 0, std::nullopt),
      "ST_InteriorRingN only applies to Polygon. Input type is: Point");
}

TEST_F(GeometryFunctionsTest, testStNumInteriorRing) {
  const auto testStNumInteriorRingFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<int64_t>& expected) {
        std::optional<int64_t> result = evaluateOnce<int64_t>(
            "ST_NumInteriorRing(ST_GeometryFromText(c0))", wkt);

        if (expected.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  testStNumInteriorRingFunc("POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0))", 0);
  testStNumInteriorRingFunc(
      "POLYGON ((0 0, 8 0, 0 8, 0 0), (1 1, 1 5, 5 1, 1 1))", 1);
  testStNumInteriorRingFunc("POLYGON EMPTY", std::nullopt);

  VELOX_ASSERT_USER_THROW(
      testStNumInteriorRingFunc("LINESTRING (8 4, 5 7)", std::nullopt),
      "ST_NumInteriorRing only applies to Polygon. Input type is: LineString");
}

TEST_F(GeometryFunctionsTest, testStNumGeometries) {
  const auto testStNumGeometriesFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<int32_t>& expected) {
        std::optional<int32_t> result = evaluateOnce<int32_t>(
            "ST_NumGeometries(ST_GeometryFromText(c0))", wkt);

        if (expected.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  // GeometryCollections with only empty geometries always return 0
  testStNumGeometriesFunc("GEOMETRYCOLLECTION (POINT EMPTY, POINT EMPTY)", 0);
  testStNumGeometriesFunc("GEOMETRYCOLLECTION (POINT EMPTY)", 0);
  // GeometryCollections with at least 1 non-empty geometry return the number of
  // geometries
  testStNumGeometriesFunc("GEOMETRYCOLLECTION(POINT EMPTY, POINT (1 2))", 2);

  testStNumGeometriesFunc("POINT EMPTY", 0);
  testStNumGeometriesFunc("GEOMETRYCOLLECTION EMPTY", 0);
  testStNumGeometriesFunc("MULTIPOLYGON EMPTY", 0);
  testStNumGeometriesFunc("POINT (1 2)", 1);
  testStNumGeometriesFunc(
      "LINESTRING(77.29 29.07,77.42 29.26,77.27 29.31,77.29 29.07)", 1);
  testStNumGeometriesFunc("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))", 1);
  testStNumGeometriesFunc("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", 4);
  testStNumGeometriesFunc("MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))", 2);
  testStNumGeometriesFunc(
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((2 4, 2 6, 6 6, 6 4, 2 4)))",
      2);
  testStNumGeometriesFunc(
      "GEOMETRYCOLLECTION(POINT(2 3), LINESTRING (2 3, 3 4))", 2);
}

TEST_F(GeometryFunctionsTest, testStConvexHull) {
  const auto testStConvexHullFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "ST_AsText(ST_ConvexHull(ST_GeometryFromText(c0)))", wkt);

        if (expected.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  // test empty geometry
  testStConvexHullFunc("POINT EMPTY", "POINT EMPTY");
  testStConvexHullFunc(
      "GEOMETRYCOLLECTION (POINT (1 1), POINT EMPTY)", "POINT (1 1)");
  testStConvexHullFunc(
      "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (1 1), GEOMETRYCOLLECTION (POINT (1 5), POINT (4 5), GEOMETRYCOLLECTION (POINT (3 4), POINT EMPTY))))",
      "POLYGON ((1 1, 1 5, 4 5, 1 1))");

  // test single geometry
  testStConvexHullFunc("POINT (1 1)", "POINT (1 1)");
  testStConvexHullFunc(
      "LINESTRING (1 1, 1 9, 2 2)", "POLYGON ((1 1, 1 9, 2 2, 1 1))");

  // convex single geometry
  testStConvexHullFunc(
      "LINESTRING (1 1, 1 9, 2 2, 1 1)", "POLYGON ((1 1, 1 9, 2 2, 1 1))");
  testStConvexHullFunc(
      "POLYGON ((0 0, 0 3, 2 4, 4 2, 3 0, 0 0))",
      "POLYGON ((0 0, 0 3, 2 4, 4 2, 3 0, 0 0))");

  // non-convex geometry
  testStConvexHullFunc(
      "LINESTRING (1 1, 1 9, 2 2, 1 1, 4 0)", "POLYGON ((4 0, 1 1, 1 9, 4 0))");
  testStConvexHullFunc(
      "POLYGON ((0 0, 0 3, 4 4, 1 1, 3 0, 0 0))",
      "POLYGON ((0 0, 0 3, 4 4, 3 0, 0 0))");

  // all points are on the same line
  testStConvexHullFunc(
      "LINESTRING (20 20, 30 30)", "LINESTRING (20 20, 30 30)");
  testStConvexHullFunc(
      "MULTILINESTRING ((0 0, 3 3), (1 1, 2 2), (2 2, 4 4), (5 5, 8 8))",
      "LINESTRING (0 0, 8 8)");
  testStConvexHullFunc(
      "MULTIPOINT (0 1, 1 2, 2 3, 3 4, 4 5, 5 6)", "LINESTRING (0 1, 5 6)");
  testStConvexHullFunc(
      "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (2 2), POINT (1 1)), POINT(3 3))",
      "LINESTRING (1 1, 3 3)");

  // not all points are on the same line
  testStConvexHullFunc(
      "MULTILINESTRING ((1 1, 5 1, 6 6), (2 4, 4 0), (2 -4, 4 4), (3 -2, 4 -3))",
      "POLYGON ((2 -4, 1 1, 2 4, 6 6, 5 1, 4 -3, 2 -4))");
  testStConvexHullFunc(
      "MULTIPOINT (0 2, 1 0, 3 0, 4 0, 4 2, 2 2, 2 4)",
      "POLYGON ((1 0, 0 2, 2 4, 4 2, 4 0, 1 0))");
  testStConvexHullFunc(
      "MULTIPOLYGON (((0 3, 2 0, 3 6, 0 3), (2 1, 2 3, 5 3, 5 1, 2 1), (1 7, 2 4, 4 2, 5 6, 3 8, 1 7)))",
      "POLYGON ((2 0, 0 3, 1 7, 3 8, 5 6, 5 1, 2 0))");
  testStConvexHullFunc(
      "GEOMETRYCOLLECTION (POINT (2 3), LINESTRING (2 8, 7 10), POINT (8 10), POLYGON ((4 4, 4 8, 9 8, 6 6, 6 4, 8 3, 6 1, 4 4)), POINT (4 2), LINESTRING (3 6, 5 5), POLYGON ((7 5, 7 6, 8 6, 8 5, 7 5)))",
      "POLYGON ((6 1, 2 3, 2 8, 7 10, 8 10, 9 8, 8 3, 6 1))");
  testStConvexHullFunc(
      "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (2 3), LINESTRING (2 8, 7 10), GEOMETRYCOLLECTION (POINT (8 10))), POLYGON ((4 4, 4 8, 9 8, 6 6, 6 4, 8 3, 6 1, 4 4)), POINT (4 2), LINESTRING (3 6, 5 5), POLYGON ((7 5, 7 6, 8 6, 8 5, 7 5)))",
      "POLYGON ((6 1, 2 3, 2 8, 7 10, 8 10, 9 8, 8 3, 6 1))");

  // single-element multi-geometries and geometry collections
  testStConvexHullFunc(
      "MULTILINESTRING ((1 1, 5 1, 6 6))", "POLYGON ((1 1, 6 6, 5 1, 1 1))");
  testStConvexHullFunc(
      "MULTILINESTRING ((1 1, 5 1, 1 4, 5 4))",
      "POLYGON ((1 1, 1 4, 5 4, 5 1, 1 1))");
  testStConvexHullFunc("MULTIPOINT (0 2)", "POINT (0 2)");
  testStConvexHullFunc(
      "MULTIPOLYGON (((0 3, 3 6, 2 0, 0 3)))",
      "POLYGON ((2 0, 0 3, 3 6, 2 0))");
  testStConvexHullFunc(
      "MULTIPOLYGON (((0 0, 4 0, 4 4, 0 4, 2 2, 0 0)))",
      "POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0))");
  testStConvexHullFunc("GEOMETRYCOLLECTION (POINT (2 3))", "POINT (2 3)");
  testStConvexHullFunc(
      "GEOMETRYCOLLECTION (LINESTRING (1 1, 5 1, 6 6))",
      "POLYGON ((1 1, 6 6, 5 1, 1 1))");
  testStConvexHullFunc(
      "GEOMETRYCOLLECTION (LINESTRING (1 1, 5 1, 1 4, 5 4))",
      "POLYGON ((1 1, 1 4, 5 4, 5 1, 1 1))");
  testStConvexHullFunc(
      "GEOMETRYCOLLECTION (POLYGON ((0 3, 3 6, 2 0, 0 3)))",
      "POLYGON ((2 0, 0 3, 3 6, 2 0))");
  testStConvexHullFunc(
      "GEOMETRYCOLLECTION (POLYGON ((0 0, 4 0, 4 4, 0 4, 2 2, 0 0)))",
      "POLYGON ((0 0, 0 4, 4 4, 4 0, 0 0))");
}

TEST_F(GeometryFunctionsTest, testStCoordDim) {
  const auto testStCoordDimFunc = [&](const std::optional<std::string>& wkt,
                                      const std::optional<int8_t>& expected) {
    std::optional<int8_t> result =
        evaluateOnce<int8_t>("ST_CoordDim(ST_GeometryFromText(c0))", wkt);

    if (expected.has_value()) {
      ASSERT_TRUE(result.has_value());
      ASSERT_EQ(result.value(), expected.value());
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  testStCoordDimFunc("POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))", 2);
  testStCoordDimFunc("POLYGON EMPTY))", 2);
  testStCoordDimFunc("LINESTRING (1 1, 1 2)", 2);
  testStCoordDimFunc("POINT (1 4)", 2);

  testStCoordDimFunc("LINESTRING EMPTY", 2);
}

TEST_F(GeometryFunctionsTest, testStDimension) {
  const auto testStDimensionFunc = [&](const std::optional<std::string>& wkt,
                                       const std::optional<int8_t>& expected) {
    std::optional<int8_t> result =
        evaluateOnce<int8_t>("ST_Dimension(ST_GeometryFromText(c0))", wkt);

    if (expected.has_value()) {
      ASSERT_TRUE(result.has_value());
      ASSERT_EQ(result.value(), expected.value());
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  testStDimensionFunc("POLYGON EMPTY", 2);
  testStDimensionFunc("POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))", 2);
  testStDimensionFunc("LINESTRING EMPTY", 1);
  testStDimensionFunc("POINT (1 4))", 0);
}

TEST_F(GeometryFunctionsTest, testStBuffer) {
  const auto testStBufferFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<double>& distance,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "ST_AsText(ST_Buffer(ST_GeometryFromText(c0), c1))", wkt, distance);

        if (expected.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  testStBufferFunc(
      "POINT (0 0)",
      0.5,
      "POLYGON ((0.5 0, 0.4903926402016152 -0.0975451610080641, 0.4619397662556434 -0.1913417161825449, 0.4157348061512726 -0.2777851165098011, 0.3535533905932738 -0.3535533905932737, 0.2777851165098011 -0.4157348061512726, 0.1913417161825449 -0.4619397662556434, 0.0975451610080642 -0.4903926402016152, 0 -0.5, -0.0975451610080641 -0.4903926402016152, -0.1913417161825449 -0.4619397662556434, -0.277785116509801 -0.4157348061512727, -0.3535533905932737 -0.3535533905932738, -0.4157348061512727 -0.2777851165098011, -0.4619397662556434 -0.191341716182545, -0.4903926402016152 -0.0975451610080643, -0.5 -0.0000000000000001, -0.4903926402016152 0.0975451610080642, -0.4619397662556434 0.1913417161825448, -0.4157348061512727 0.277785116509801, -0.3535533905932738 0.3535533905932737, -0.2777851165098011 0.4157348061512726, -0.1913417161825452 0.4619397662556433, -0.0975451610080643 0.4903926402016152, -0.0000000000000001 0.5, 0.0975451610080642 0.4903926402016152, 0.191341716182545 0.4619397662556433, 0.2777851165098009 0.4157348061512727, 0.3535533905932737 0.3535533905932738, 0.4157348061512726 0.2777851165098011, 0.4619397662556433 0.1913417161825452, 0.4903926402016152 0.0975451610080644, 0.5 0))");
  testStBufferFunc(
      "LINESTRING (0 0, 1 1, 2 0.5)",
      .2,
      "POLYGON ((0.8585786437626906 1.1414213562373094, 0.8908600605480863 1.167596162296255, 0.9278541681368628 1.1865341227356967, 0.9679635513986066 1.1974174915274993, 1.0094562767938988 1.1997763219933664, 1.050540677712335 1.1935087592239118, 1.0894427190999916 1.1788854381999831, 2.0894427190999916 0.6788854381999831, 2.1226229200749436 0.6579987957938098, 2.1510907909991412 0.6310403482720258, 2.173752327557934 0.5990460936544217, 2.189736659610103 0.5632455532033676, 2.198429518239 0.5250145216112229, 2.1994968417625285 0.4858221959818642, 2.192897613536241 0.4471747154099183, 2.178885438199983 0.4105572809000084, 2.1579987957938096 0.3773770799250564, 2.131040348272026 0.3489092090008588, 2.099046093654422 0.3262476724420662, 2.0632455532033678 0.3102633403898972, 2.0250145216112228 0.3015704817609999, 1.985822195981864 0.3005031582374715, 1.9471747154099184 0.3071023864637593, 1.9105572809000084 0.3211145618000168, 1.0394906098164267 0.7566478973418077, 0.1414213562373095 -0.1414213562373095, 0.1111140466039205 -0.1662939224605091, 0.076536686473018 -0.1847759065022574, 0.0390180644032257 -0.1961570560806461, 0 -0.2, -0.0390180644032256 -0.1961570560806461, -0.076536686473018 -0.1847759065022574, -0.1111140466039204 -0.1662939224605091, -0.1414213562373095 -0.1414213562373095, -0.1662939224605091 -0.1111140466039204, -0.1847759065022574 -0.076536686473018, -0.1961570560806461 -0.0390180644032257, -0.2 0, -0.1961570560806461 0.0390180644032257, -0.1847759065022574 0.0765366864730179, -0.1662939224605091 0.1111140466039204, -0.1414213562373095 0.1414213562373095, 0.8585786437626906 1.1414213562373094))");
  testStBufferFunc(
      "POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0))",
      1.2,
      "POLYGON ((0 -1.2, -0.2341083864193544 -1.1769423364838763, -0.4592201188381084 -1.1086554390135437, -0.6666842796235226 -0.9977635347630542, -0.8485281374238572 -0.8485281374238569, -0.9977635347630545 -0.6666842796235223, -1.1086554390135441 -0.4592201188381076, -1.1769423364838765 -0.234108386419354, -1.2 0, -1.2 5, -1.1769423364838765 5.234108386419354, -1.1086554390135441 5.4592201188381075, -0.9977635347630543 5.666684279623523, -0.8485281374238569 5.848528137423857, -0.6666842796235223 5.997763534763054, -0.4592201188381076 6.108655439013544, -0.2341083864193538 6.176942336483877, 0 6.2, 5 6.2, 5.234108386419354 6.176942336483877, 5.4592201188381075 6.108655439013544, 5.666684279623523 5.997763534763054, 5.848528137423857 5.848528137423857, 5.997763534763054 5.666684279623523, 6.108655439013544 5.4592201188381075, 6.176942336483877 5.234108386419354, 6.2 5, 6.2 0, 6.176942336483877 -0.2341083864193539, 6.108655439013544 -0.4592201188381077, 5.997763534763054 -0.6666842796235226, 5.848528137423857 -0.8485281374238569, 5.666684279623523 -0.9977635347630542, 5.4592201188381075 -1.1086554390135441, 5.234108386419354 -1.1769423364838765, 5 -1.2, 0 -1.2))");

  // Zero distance
  testStBufferFunc("POINT (0 0)", 0, "POINT (0 0)");
  testStBufferFunc(
      "LINESTRING (0 0, 1 1, 2 0.5)", 0, "LINESTRING (0 0, 1 1, 2 0.5)");
  testStBufferFunc(
      "POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0))",
      0,
      "POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0))");

  // GeometryCollection
  testStBufferFunc(
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))",
      .2,
      "MULTIPOLYGON (((5 1.2, 5.039018064403225 1.196157056080646, 5.076536686473018 1.1847759065022574, 5.11111404660392 1.1662939224605091, 5.141421356237309 1.1414213562373094, 5.166293922460509 1.1111140466039204, 5.184775906502257 1.076536686473018, 5.196157056080646 1.0390180644032256, 5.2 1, 5.196157056080646 0.9609819355967744, 5.184775906502257 0.9234633135269821, 5.166293922460509 0.8888859533960796, 5.141421356237309 0.8585786437626906, 5.11111404660392 0.8337060775394909, 5.076536686473018 0.8152240934977426, 5.039018064403225 0.803842943919354, 5 0.8, 1 0.8, 0.9609819355967743 0.803842943919354, 0.923463313526982 0.8152240934977427, 0.8888859533960796 0.8337060775394909, 0.8585786437626904 0.8585786437626906, 0.8337060775394909 0.8888859533960796, 0.8152240934977426 0.9234633135269821, 0.803842943919354 0.9609819355967744, 0.8 1, 0.803842943919354 1.0390180644032256, 0.8152240934977426 1.076536686473018, 0.8337060775394909 1.1111140466039204, 0.8585786437626906 1.1414213562373094, 0.8888859533960796 1.1662939224605091, 0.9234633135269821 1.1847759065022574, 0.9609819355967744 1.196157056080646, 1 1.2, 5 1.2)), ((4 4.2, 4.039018064403225 4.196157056080646, 4.076536686473018 4.184775906502257, 4.11111404660392 4.166293922460509, 4.141421356237309 4.141421356237309, 4.166293922460509 4.11111404660392, 4.184775906502257 4.076536686473018, 4.196157056080646 4.039018064403225, 4.2 4, 4.196157056080646 3.960981935596774, 4.184775906502257 3.923463313526982, 4.166293922460509 3.8888859533960796, 4.141421356237309 3.8585786437626903, 4.11111404660392 3.833706077539491, 4.076536686473018 3.8152240934977426, 4.039018064403225 3.8038429439193537, 4 3.8, 2 3.8, 1.9609819355967744 3.8038429439193537, 1.9234633135269819 3.8152240934977426, 1.8888859533960796 3.833706077539491, 1.8585786437626906 3.8585786437626903, 1.8337060775394909 3.8888859533960796, 1.8152240934977426 3.923463313526982, 1.803842943919354 3.960981935596774, 1.8 4, 1.803842943919354 4.039018064403225, 1.8152240934977426 4.076536686473018, 1.8337060775394909 4.11111404660392, 1.8585786437626906 4.141421356237309, 1.8888859533960796 4.166293922460509, 1.923463313526982 4.184775906502257, 1.9609819355967744 4.196157056080646, 2 4.2, 4 4.2)))");

  // Empty
  testStBufferFunc("POINT EMPTY", .2, std::nullopt);

  // Negative distance
  VELOX_ASSERT_USER_THROW(
      testStBufferFunc("POINT (0 0)", -1.2, "POINT (0 0)"),
      "Provided distance must not be negative. Provided distance: -1.2");
}

TEST_F(GeometryFunctionsTest, testStExteriorRing) {
  const auto testStExteriorRingFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "ST_AsText(ST_ExteriorRing(ST_GeometryFromText(c0)))", wkt);

        if (expected.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  testStExteriorRingFunc("POLYGON EMPTY", std::nullopt);
  testStExteriorRingFunc(
      "POLYGON ((1 1, 1 4, 4 1, 1 1))", "LINESTRING (1 1, 1 4, 4 1, 1 1)");

  testStExteriorRingFunc(
      "POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1))",
      "LINESTRING (0 0, 0 5, 5 5, 5 0, 0 0)");

  VELOX_ASSERT_USER_THROW(
      testStExteriorRingFunc("LINESTRING (1 1, 2 2, 1 3)", std::nullopt),
      "ST_ExteriorRing only applies to Polygon. Input type is: LineString");
  VELOX_ASSERT_USER_THROW(
      testStExteriorRingFunc(
          "MULTIPOLYGON (((1 1, 2 2, 1 3, 1 1)), ((4 4, 5 5, 4 6, 4 4)))",
          std::nullopt),
      "ST_ExteriorRing only applies to Polygon. Input type is: MultiPolygon");
}

TEST_F(GeometryFunctionsTest, testStEnvelope) {
  const auto testStEnvelopeFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "ST_AsText(st_envelope(ST_GeometryFromText(c0)))", wkt);

        if (expected.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  testStEnvelopeFunc(
      "MULTIPOINT (1 2, 2 4, 3 6, 4 8)", "POLYGON ((1 2, 1 8, 4 8, 4 2, 1 2))");
  testStEnvelopeFunc(
      "LINESTRING (1 1, 2 2, 1 3)", "POLYGON ((1 1, 1 3, 2 3, 2 1, 1 1))");
  testStEnvelopeFunc(
      "LINESTRING (8 4, 5 7)", "POLYGON ((5 4, 5 7, 8 7, 8 4, 5 4))");
  testStEnvelopeFunc(
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))",
      "POLYGON ((1 1, 1 4, 5 4, 5 1, 1 1))");
  testStEnvelopeFunc(
      "POLYGON ((1 1, 4 1, 1 4, 1 1))", "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))");
  testStEnvelopeFunc(
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((0 0, 0 2, 2 2, 2 0, 0 0)))",
      "POLYGON ((0 0, 0 3, 3 3, 3 0, 0 0))");
  testStEnvelopeFunc(
      "GEOMETRYCOLLECTION (POINT (5 1), LINESTRING (3 4, 4 4))",
      "POLYGON ((3 1, 3 4, 5 4, 5 1, 3 1))");
  testStEnvelopeFunc(
      "MULTIPOLYGON (((119.094024 -27.2871725, 119.094024 -27.2846569, 119.094024 -27.2868119, 119.094024 -27.2871725)))",
      "POLYGON ((119.094024 -27.2871725, 119.094024 -27.2846569, 119.094024 -27.2846569, 119.094024 -27.2871725, 119.094024 -27.2871725))");

  testStEnvelopeFunc("POLYGON EMPTY", "POLYGON EMPTY");
  testStEnvelopeFunc("MULTIPOLYGON EMPTY", "POLYGON EMPTY");
  testStEnvelopeFunc("GEOMETRYCOLLECTION EMPTY", "POLYGON EMPTY");
  testStEnvelopeFunc("POINT EMPTY", "POLYGON EMPTY");
  testStEnvelopeFunc("MULTIPOINT EMPTY", "POLYGON EMPTY");
  testStEnvelopeFunc("LINESTRING EMPTY", "POLYGON EMPTY");
  testStEnvelopeFunc("MULTILINESTRING EMPTY", "POLYGON EMPTY");
}

TEST_F(GeometryFunctionsTest, testStPoints) {
  const auto testStPointsFunc = [&](const std::optional<std::string>& wkt,
                                    const std::optional<std::vector<
                                        std::optional<std::string>>>&
                                        expectedPoints) {
    auto input = makeSingleStringInputRow(wkt);

    facebook::velox::VectorPtr output = evaluate(
        "transform(ST_Points(ST_GeometryFromText(c0)), x -> substr(ST_AsText(x), 7))",
        input);

    auto arrayVector =
        std::dynamic_pointer_cast<facebook::velox::ArrayVector>(output);

    ASSERT_TRUE(arrayVector != nullptr);

    auto expected = makeNullableArrayVector<std::string>({{expectedPoints}});
    facebook::velox::test::assertEqualVectors(expected, output);
  };

  testStPointsFunc("LINESTRING (0 0, 0 0)", {{"(0 0)", "(0 0)"}});
  testStPointsFunc("LINESTRING (8 4, 3 9, 8 4)", {{"(8 4)", "(3 9)", "(8 4)"}});
  testStPointsFunc("LINESTRING (8 4, 3 9, 5 6)", {{"(8 4)", "(3 9)", "(5 6)"}});
  testStPointsFunc(
      "LINESTRING (8 4, 3 9, 5 6, 3 9, 8 4)",
      {{"(8 4)", "(3 9)", "(5 6)", "(3 9)", "(8 4)"}});

  testStPointsFunc(
      "POLYGON ((8 4, 3 9, 5 6, 8 4))", {{"(8 4)", "(5 6)", "(3 9)", "(8 4)"}});
  testStPointsFunc(
      "POLYGON ((8 4, 3 9, 5 6, 7 2, 8 4))",
      {{"(8 4)", "(7 2)", "(5 6)", "(3 9)", "(8 4)"}});

  testStPointsFunc("POINT (0 0)", {{"(0 0)"}});
  testStPointsFunc("POINT (0 1)", {{"(0 1)"}});

  testStPointsFunc("MULTIPOINT (0 0)", {{"(0 0)"}});
  testStPointsFunc("MULTIPOINT (0 0, 1 2)", {{"(0 0)", "(1 2)"}});

  testStPointsFunc(
      "MULTILINESTRING ((0 0, 1 1), (2 3, 3 2))",
      {{"(0 0)", "(1 1)", "(2 3)", "(3 2)"}});
  testStPointsFunc(
      "MULTILINESTRING ((0 0, 1 1, 1 2), (2 3, 3 2, 5 4))",
      {{"(0 0)", "(1 1)", "(1 2)", "(2 3)", "(3 2)", "(5 4)"}});
  testStPointsFunc(
      "MULTILINESTRING ((0 0, 1 1, 1 2), (1 2, 3 2, 5 4))",
      {{"(0 0)", "(1 1)", "(1 2)", "(1 2)", "(3 2)", "(5 4)"}});

  testStPointsFunc(
      "MULTIPOLYGON (((0 0, 4 0, 4 4, 0 4, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1)), ((-1 -1, -1 -2, -2 -2, -2 -1, -1 -1)))",
      {{
          "(0 0)",
          "(0 4)",
          "(4 4)",
          "(4 0)",
          "(0 0)",
          "(1 1)",
          "(2 1)",
          "(2 2)",
          "(1 2)",
          "(1 1)",
          "(-1 -1)",
          "(-1 -2)",
          "(-2 -2)",
          "(-2 -1)",
          "(-1 -1)",
      }});

  testStPointsFunc(
      "GEOMETRYCOLLECTION(POINT(0 1),LINESTRING(0 3,3 4),POLYGON((2 0,2 3,0 2,2 0)),POLYGON((3 0,3 3,6 3,6 0,3 0),(5 1,4 2,5 2,5 1)),MULTIPOLYGON(((0 5,0 8,4 8,4 5,0 5),(1 6,3 6,2 7,1 6)),((5 4,5 8,6 7,5 4))))",
      {{"(0 1)", "(0 3)", "(3 4)", "(2 0)", "(0 2)", "(2 3)", "(2 0)", "(3 0)",
        "(3 3)", "(6 3)", "(6 0)", "(3 0)", "(5 1)", "(5 2)", "(4 2)", "(5 1)",
        "(0 5)", "(0 8)", "(4 8)", "(4 5)", "(0 5)", "(1 6)", "(3 6)", "(2 7)",
        "(1 6)", "(5 4)", "(5 8)", "(6 7)", "(5 4)"}});

  const auto testStPointsNullAndEmptyFunc =
      [&](const std::optional<std::string>& wkt) {
        std::optional<bool> result = evaluateOnce<bool>(
            "ST_Points(ST_GeometryFromText(c0)) IS NULL", wkt);

        ASSERT_TRUE(result.has_value());
        ASSERT_TRUE(result.value());
      };

  testStPointsNullAndEmptyFunc("POINT EMPTY");
  testStPointsNullAndEmptyFunc("LINESTRING EMPTY");
  testStPointsNullAndEmptyFunc("POLYGON EMPTY");
  testStPointsNullAndEmptyFunc("MULTIPOINT EMPTY");
  testStPointsNullAndEmptyFunc("MULTILINESTRING EMPTY");
  testStPointsNullAndEmptyFunc("MULTIPOLYGON EMPTY");
  testStPointsNullAndEmptyFunc("GEOMETRYCOLLECTION EMPTY");
  testStPointsNullAndEmptyFunc(std::nullopt);
}

TEST_F(GeometryFunctionsTest, testStEnvelopeAsPts) {
  const auto testStEnvelopeAsPtsFunc = [&](const std::optional<std::string>&
                                               wkt,
                                           const std::optional<std::vector<
                                               std::optional<std::string>>>&
                                               expectedPoints) {
    auto input = makeSingleStringInputRow(wkt);

    facebook::velox::VectorPtr output = evaluate(
        "transform(ST_EnvelopeAsPts(ST_GeometryFromText(c0)), x -> substr(ST_AsText(x), 7))",
        input);

    auto arrayVector =
        std::dynamic_pointer_cast<facebook::velox::ArrayVector>(output);

    ASSERT_TRUE(arrayVector != nullptr);

    std::vector<std::vector<std::optional<std::string>>> vec = {
        expectedPoints.value()};
    auto expected = makeNullableArrayVector<std::string>(vec);
    facebook::velox::test::assertEqualVectors(expected, output);
  };

  testStEnvelopeAsPtsFunc(
      "MULTIPOINT (1 2, 2 4, 3 6, 4 8)", {{"(1 2)", "(4 8)"}});
  testStEnvelopeAsPtsFunc("LINESTRING (1 1, 2 2, 1 3)", {{"(1 1)", "(2 3)"}});
  testStEnvelopeAsPtsFunc("LINESTRING (8 4, 5 7)", {{"(5 4)", "(8 7)"}});
  testStEnvelopeAsPtsFunc(
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))", {{"(1 1)", "(5 4)"}});
  testStEnvelopeAsPtsFunc(
      "POLYGON ((1 1, 4 1, 1 4, 1 1))", {{"(1 1)", "(4 4)"}});
  testStEnvelopeAsPtsFunc(
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((0 0, 0 2, 2 2, 2 0, 0 0)))",
      {{"(0 0)", "(3 3)"}});
  testStEnvelopeAsPtsFunc(
      "GEOMETRYCOLLECTION (POINT (5 1), LINESTRING (3 4, 4 4))",
      {{"(3 1)", "(5 4)"}});
  testStEnvelopeAsPtsFunc("POINT (1 2)", {{"(1 2)", "(1 2)"}});

  const auto testStEnvelopeAsPtsNullAndEmptyFunc =
      [&](const std::optional<std::string>& wkt) {
        std::optional<bool> result = evaluateOnce<bool>(
            "ST_EnvelopeAsPts(ST_GeometryFromText(c0)) IS NULL", wkt);

        ASSERT_TRUE(result.has_value());
        ASSERT_TRUE(result.value());
      };

  testStEnvelopeAsPtsNullAndEmptyFunc("POINT EMPTY");
  testStEnvelopeAsPtsNullAndEmptyFunc("LINESTRING EMPTY");
  testStEnvelopeAsPtsNullAndEmptyFunc("POLYGON EMPTY");
  testStEnvelopeAsPtsNullAndEmptyFunc("MULTIPOINT EMPTY");
  testStEnvelopeAsPtsNullAndEmptyFunc("MULTILINESTRING EMPTY");
  testStEnvelopeAsPtsNullAndEmptyFunc("MULTIPOLYGON EMPTY");
  testStEnvelopeAsPtsNullAndEmptyFunc("GEOMETRYCOLLECTION EMPTY");
  testStEnvelopeAsPtsNullAndEmptyFunc(std::nullopt);
}

TEST_F(GeometryFunctionsTest, testStNumPoints) {
  const auto testStNumPointsFunc = [&](const std::optional<std::string>& wkt,
                                       const std::optional<int64_t>& expected) {
    std::optional<int64_t> result =
        evaluateOnce<int64_t>("ST_NumPoints(ST_GeometryFromText(c0))", wkt);

    if (expected.has_value()) {
      ASSERT_TRUE(result.has_value());
      ASSERT_EQ(result.value(), expected.value());
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  testStNumPointsFunc("POINT EMPTY", 0);
  testStNumPointsFunc("MULTIPOINT EMPTY", 0);
  testStNumPointsFunc("LINESTRING EMPTY", 0);
  testStNumPointsFunc("MULTILINESTRING EMPTY", 0);
  testStNumPointsFunc("POLYGON EMPTY", 0);
  testStNumPointsFunc("MULTIPOLYGON EMPTY", 0);
  testStNumPointsFunc("GEOMETRYCOLLECTION EMPTY", 0);

  testStNumPointsFunc("POINT (1 2)", 1);
  testStNumPointsFunc("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", 4);
  testStNumPointsFunc("LINESTRING (8 4, 5 7)", 2);
  testStNumPointsFunc("MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))", 4);
  testStNumPointsFunc("POLYGON ((0 0, 8 0, 0 8, 0 0))", 3);
  testStNumPointsFunc(
      "POLYGON ((0 0, 8 0, 0 8, 0 0), (1 1, 1 5, 5 1, 1 1))", 6);
  testStNumPointsFunc(
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((2 4, 2 6, 6 6, 6 4, 2 4)))",
      8);
  testStNumPointsFunc(
      "GEOMETRYCOLLECTION (POINT (1 1), GEOMETRYCOLLECTION (LINESTRING (0 0, 1 1), GEOMETRYCOLLECTION (POLYGON ((2 2, 2 3, 3 3, 3 2, 2 2)))))",
      7);
}

TEST_F(GeometryFunctionsTest, testGeometryNearestPoints) {
  const auto testGeometryNearestPointsFunc = [&](const std::optional<
                                                     std::string>& wkt1,
                                                 const std::optional<
                                                     std::string>& wkt2,
                                                 const std::optional<
                                                     std::vector<std::optional<
                                                         std::string>>>&
                                                     expectedPoints) {
    auto inputVec1 = makeNullableFlatVector<std::string>({wkt1});
    auto inputVec2 = makeNullableFlatVector<std::string>({wkt2});
    auto input = makeRowVector({inputVec1, inputVec2});

    facebook::velox::VectorPtr output = evaluate(
        "transform(geometry_nearest_points(ST_GeometryFromText(c0), ST_GeometryFromText(c1)), x -> substr(ST_AsText(x), 7))",
        input);

    auto arrayVector =
        std::dynamic_pointer_cast<facebook::velox::ArrayVector>(output);

    ASSERT_TRUE(arrayVector != nullptr);

    std::vector<std::vector<std::optional<std::string>>> vec = {
        expectedPoints.value()};
    auto expected = makeNullableArrayVector<std::string>(vec);
    facebook::velox::test::assertEqualVectors(expected, output);
  };

  testGeometryNearestPointsFunc(
      "POINT (50 100)", "POINT (150 150)", {{"(50 100)", "(150 150)"}});
  testGeometryNearestPointsFunc(
      "MULTIPOINT (50 100, 50 200)",
      "POINT (50 100)",
      {{"(50 100)", "(50 100)"}});
  testGeometryNearestPointsFunc(
      "LINESTRING (50 100, 50 200)",
      "LINESTRING (10 10, 20 20)",
      {{"(50 100)", "(20 20)"}});
  testGeometryNearestPointsFunc(
      "MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))",
      "LINESTRING (10 20, 20 50)",
      {{"(4 4)", "(10 20)"}});
  testGeometryNearestPointsFunc(
      "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
      "POLYGON ((4 4, 4 5, 5 5, 5 4, 4 4))",
      {{"(3 3)", "(4 4)"}});
  testGeometryNearestPointsFunc(
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((0 0, 0 2, 2 2, 2 0, 0 0)))",
      "POLYGON ((10 100, 30 10, 30 100, 10 100))",
      {{"(3 3)", "(30 10)"}});
  testGeometryNearestPointsFunc(
      "GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (0 20, 20 0))",
      "POLYGON ((5 5, 5 6, 6 6, 6 5, 5 5))",
      {{"(10 10)", "(6 6)"}});

  const auto noNearestPointsFunc = [&](const std::optional<std::string>& wkt1,
                                       const std::optional<std::string>& wkt2) {
    std::optional<bool> result = evaluateOnce<bool>(
        "geometry_nearest_points(ST_GeometryFromText(c0), ST_GeometryFromText(c1)) IS NULL",
        wkt1,
        wkt2);

    ASSERT_TRUE(result.has_value());
    ASSERT_TRUE(result.value());
  };

  noNearestPointsFunc("POINT EMPTY", "POINT (150 150)");
  noNearestPointsFunc("POINT (50 100)", "POINT EMPTY");
  noNearestPointsFunc("POINT EMPTY", "POINT EMPTY");
  noNearestPointsFunc("MULTIPOINT EMPTY", "POINT (50 100)");
  noNearestPointsFunc("LINESTRING (50 100, 50 200)", "LINESTRING EMPTY");
  noNearestPointsFunc("MULTILINESTRING EMPTY", "LINESTRING (10 20, 20 50)");
  noNearestPointsFunc("POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))", "POLYGON EMPTY");
  noNearestPointsFunc(
      "MULTIPOLYGON EMPTY", "POLYGON ((10 100, 30 10, 30 100, 10 100))");

  noNearestPointsFunc(
      std::nullopt, "POLYGON ((10 100, 30 10, 30 100, 10 100))");
  noNearestPointsFunc("LINESTRING (50 100, 50 200)", std::nullopt);
  noNearestPointsFunc(std::nullopt, std::nullopt);
}

TEST_F(GeometryFunctionsTest, testLineLocatePoint) {
  const auto testLineLocatePointFunc = [&](const std::optional<std::string>&
                                               lineWkt,
                                           const std::optional<std::string>&
                                               pointWkt,
                                           const std::optional<double>&
                                               expected) {
    std::optional<double> result = evaluateOnce<double>(
        "line_locate_point(ST_GeometryFromText(c0), ST_GeometryFromText(c1))",
        lineWkt,
        pointWkt);

    if (expected.has_value()) {
      ASSERT_TRUE(result.has_value());
      ASSERT_EQ(result.value(), expected.value());
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  testLineLocatePointFunc("LINESTRING (0 0, 0 1)", "POINT (0 0.2)", .2);
  testLineLocatePointFunc("LINESTRING (0 0, 0 1)", "POINT (0 0)", 0.0);
  testLineLocatePointFunc("LINESTRING (0 0, 0 1)", "POINT (0 -1)", 0.0);
  testLineLocatePointFunc("LINESTRING (0 0, 0 1)", "POINT (0 1)", 1.0);
  testLineLocatePointFunc("LINESTRING (0 0, 0 1)", "POINT (0 2)", 1.0);
  testLineLocatePointFunc(
      "LINESTRING (0 0, 0 1, 2 1)", "POINT (0 0.2)", 0.06666666666666667);
  testLineLocatePointFunc(
      "LINESTRING (0 0, 0 1, 2 1)", "POINT (0.9 1)", 0.6333333333333333);
  testLineLocatePointFunc("LINESTRING (1 3, 5 4)", "POINT (1 3)", 0.0);
  testLineLocatePointFunc(
      "LINESTRING (1 3, 5 4)", "POINT (2 3)", 0.23529411764705882);
  testLineLocatePointFunc("LINESTRING (1 3, 5 4)", "POINT (5 4)", 1.0);
  testLineLocatePointFunc(
      "MULTILINESTRING ((0 0, 0 1), (2 2, 4 2))",
      "POINT (3 1)",
      0.6666666666666666);

  testLineLocatePointFunc("LINESTRING EMPTY", "POINT (5 4)", std::nullopt);
  testLineLocatePointFunc("LINESTRING (1 3, 5 4)", "POINT EMPTY", std::nullopt);

  testLineLocatePointFunc(std::nullopt, "POINT (5 4)", std::nullopt);
  testLineLocatePointFunc("LINESTRING (1 3, 5 4)", std::nullopt, std::nullopt);

  VELOX_ASSERT_USER_THROW(
      testLineLocatePointFunc(
          "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))", "POINT (5 4)", std::nullopt),
      "First argument to line_locate_point must be a LineString or a MultiLineString. Got: Polygon");

  VELOX_ASSERT_USER_THROW(
      testLineLocatePointFunc(
          "LINESTRING (0 0, 0 1)", "LINESTRING (1 3, 5 4)", std::nullopt),
      "Second argument to line_locate_point must be a Point. Got: LineString");
}

TEST_F(GeometryFunctionsTest, testLineInterpolatePoint) {
  const auto testLineInterpolatePointFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<double>& fraction,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "ST_AsText(line_interpolate_point(ST_GeometryFromText(c0), c1))",
            wkt,
            fraction);

        if (expected.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  testLineInterpolatePointFunc("LINESTRING EMPTY", .5, "POINT EMPTY");
  testLineInterpolatePointFunc("LINESTRING (0 0, 0 1)", .2, "POINT (0 0.2)");
  testLineInterpolatePointFunc("LINESTRING (0 0, 0 1)", 0.0, "POINT (0 0)");
  testLineInterpolatePointFunc("LINESTRING (0 0, 0 1)", 1.0, "POINT (0 1)");
  testLineInterpolatePointFunc(
      "LINESTRING (0 0, 0 1, 3 1)", .0625, "POINT (0 0.25)");
  testLineInterpolatePointFunc(
      "LINESTRING (0 0, 0 1, 3 1)", .75, "POINT (2 1)");
  testLineInterpolatePointFunc("LINESTRING (1 3, 5 4)", 0.0, "POINT (1 3)");
  testLineInterpolatePointFunc("LINESTRING (1 3, 5 4)", 0.25, "POINT (2 3.25)");
  testLineInterpolatePointFunc("LINESTRING (1 3, 5 4)", 1.0, "POINT (5 4)");

  VELOX_ASSERT_USER_THROW(
      testLineInterpolatePointFunc(
          "POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))", .5, std::nullopt),
      "line_interpolate_point only applies to LineString. Input type is: Polygon");

  VELOX_ASSERT_USER_THROW(
      testLineInterpolatePointFunc(
          "MULTILINESTRING ((0 0, 0 1), (2 2, 4 2))", .5, std::nullopt),
      "line_interpolate_point only applies to LineString. Input type is: MultiLineString");

  VELOX_ASSERT_USER_THROW(
      testLineInterpolatePointFunc(
          "LINESTRING (0 0, 0 1, 2 1)", -1.0, std::nullopt),
      "line_interpolate_point: Fraction must be between 0 and 1, but is -1");

  VELOX_ASSERT_USER_THROW(
      testLineInterpolatePointFunc(
          "LINESTRING (0 0, 0 1, 2 1)", 1.5, std::nullopt),
      "line_interpolate_point: Fraction must be between 0 and 1, but is 1.5");
}

TEST_F(GeometryFunctionsTest, testStInteriorRings) {
  const auto testStInteriorRingsFunc = [&](const std::optional<std::string>&
                                               wkt,
                                           const std::optional<std::vector<
                                               std::optional<std::string>>>&
                                               expectedGeoms) {
    auto input = makeSingleStringInputRow(wkt);

    facebook::velox::VectorPtr output = evaluate(
        "transform(ST_InteriorRings(ST_GeometryFromText(c0)), x -> ST_AsText(x))",
        input);

    auto arrayVector =
        std::dynamic_pointer_cast<facebook::velox::ArrayVector>(output);

    if (expectedGeoms.has_value()) {
      ASSERT_TRUE(arrayVector != nullptr);

      std::vector<std::vector<std::optional<std::string>>> vec = {
          expectedGeoms.value()};
      auto expected = makeNullableArrayVector<std::string>(vec);
      facebook::velox::test::assertEqualVectors(expected, output);
    } else {
      ASSERT_TRUE(output->isNullAt(0));
    }
  };

  testStInteriorRingsFunc(
      "POLYGON ((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))",
      {{"LINESTRING (1 1, 2 1, 2 2, 1 2, 1 1)"}});
  testStInteriorRingsFunc(
      "POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1), (3 3, 4 3, 4 4, 3 4, 3 3))",
      {{"LINESTRING (1 1, 2 1, 2 2, 1 2, 1 1)",
        "LINESTRING (3 3, 4 3, 4 4, 3 4, 3 3)"}});
  testStInteriorRingsFunc(
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))",
      facebook::velox::common::testutil::optionalEmpty);
  testStInteriorRingsFunc("POLYGON EMPTY", std::nullopt);

  VELOX_ASSERT_USER_THROW(
      testStInteriorRingsFunc("POINT (2 3)", std::nullopt),
      "ST_InteriorRings only applies to Polygon. Input type is: Point");
}

TEST_F(GeometryFunctionsTest, testStGeometries) {
  const auto testStGeometriesFunc = [&](const std::optional<std::string>& wkt,
                                        const std::optional<std::vector<
                                            std::optional<std::string>>>&
                                            expectedGeoms) {
    auto input = makeSingleStringInputRow(wkt);
    facebook::velox::VectorPtr output = evaluate(
        "transform(ST_Geometries(ST_GeometryFromText(c0)), x -> ST_AsText(x))",
        input);

    auto arrayVector =
        std::dynamic_pointer_cast<facebook::velox::ArrayVector>(output);

    if (expectedGeoms.has_value()) {
      ASSERT_TRUE(arrayVector != nullptr);

      std::vector<std::vector<std::optional<std::string>>> vec = {
          expectedGeoms.value()};
      auto expected = makeNullableArrayVector<std::string>(vec);
      facebook::velox::test::assertEqualVectors(expected, output);
    } else {
      ASSERT_TRUE(output->isNullAt(0));
    }
  };

  testStGeometriesFunc("POINT (1 5)", {{"POINT (1 5)"}});

  testStGeometriesFunc(
      "LINESTRING (77.29 29.07, 77.42 29.26, 77.27 29.31, 77.29 29.07)",
      {{"LINESTRING (77.29 29.07, 77.42 29.26, 77.27 29.31, 77.29 29.07)"}});
  testStGeometriesFunc(
      "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))",
      {{"POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))"}});
  testStGeometriesFunc(
      "MULTIPOINT (1 2, 4 8, 16 32)",
      {{"POINT (1 2)", "POINT (4 8)", "POINT (16 32)"}});
  testStGeometriesFunc(
      "MULTILINESTRING ((1 1, 2 2))", {{"LINESTRING (1 1, 2 2)"}});
  testStGeometriesFunc(
      "MULTIPOLYGON (((0 0, 0 1, 1 1, 1 0, 0 0)), ((1 1, 3 1, 3 3, 1 3, 1 1)))",
      {{"POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))",
        "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))"}});
  testStGeometriesFunc(
      "GEOMETRYCOLLECTION (POINT (2 3), LINESTRING (2 3, 3 4))",
      {{"POINT (2 3)", "LINESTRING (2 3, 3 4)"}});
  testStGeometriesFunc(
      "GEOMETRYCOLLECTION(MULTIPOINT(0 0, 1 1), GEOMETRYCOLLECTION(MULTILINESTRING((2 2, 3 3))))",
      {{"MULTIPOINT (0 0, 1 1)",
        "GEOMETRYCOLLECTION (MULTILINESTRING ((2 2, 3 3)))"}});

  std::optional<bool> emptyGeomReturnsNull = evaluateOnce<bool>(
      "ST_Geometries(ST_GeometryFromText(c0)) IS NULL",
      std::optional<std::string>("POLYGON EMPTY"));

  ASSERT_TRUE(emptyGeomReturnsNull.has_value());
  ASSERT_TRUE(emptyGeomReturnsNull.value());
}

TEST_F(GeometryFunctionsTest, testFlattenGeometryCollections) {
  const auto testFlattenGeometryCollectionsFunc = [&](const std::optional<
                                                          std::string>& wkt,
                                                      const std::optional<
                                                          std::vector<
                                                              std::optional<
                                                                  std::
                                                                      string>>>&
                                                          expectedGeoms) {
    auto input = makeSingleStringInputRow(wkt);
    facebook::velox::VectorPtr output = evaluate(
        "transform(flatten_geometry_collections(ST_GeometryFromText(c0)), x -> ST_AsText(x))",
        input);

    auto arrayVector =
        std::dynamic_pointer_cast<facebook::velox::ArrayVector>(output);

    if (expectedGeoms.has_value()) {
      ASSERT_TRUE(arrayVector != nullptr);

      std::vector<std::vector<std::optional<std::string>>> vec = {
          expectedGeoms.value()};
      auto expected = makeNullableArrayVector<std::string>(vec);
      facebook::velox::test::assertEqualVectors(expected, output);
    } else {
      ASSERT_TRUE(output->isNullAt(0));
    }
  };

  testFlattenGeometryCollectionsFunc("POINT (1 5)", {{"POINT (1 5)"}});
  testFlattenGeometryCollectionsFunc(
      "MULTIPOINT ((0 0), (1 1))", {{"MULTIPOINT (0 0, 1 1)"}});
  testFlattenGeometryCollectionsFunc(
      "GEOMETRYCOLLECTION EMPTY",
      facebook::velox::common::testutil::optionalEmpty);
  testFlattenGeometryCollectionsFunc(
      "GEOMETRYCOLLECTION (POINT EMPTY)", {{"POINT EMPTY"}});
  testFlattenGeometryCollectionsFunc(
      "GEOMETRYCOLLECTION (POINT (0 0))", {{"POINT (0 0)"}});
  testFlattenGeometryCollectionsFunc(
      "GEOMETRYCOLLECTION (POINT (0 0), GEOMETRYCOLLECTION (POINT (1 1)))",
      {{"POINT (0 0)", "POINT (1 1)"}});

  std::optional<std::string> wkt = std::nullopt;
  std::optional<bool> expectNullResult = evaluateOnce<bool>(
      "flatten_geometry_collections(ST_GeometryFromText(c0)) IS NULL", wkt);

  ASSERT_TRUE(expectNullResult.has_value());
  ASSERT_TRUE(expectNullResult.value());
}

TEST_F(GeometryFunctionsTest, testExpandEnvelope) {
  const auto testExpandEnvelopeFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<double>& distance,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "ST_AsText(expand_envelope(ST_GeometryFromText(c0), c1))",
            wkt,
            distance);

        if (wkt.has_value() && distance.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  testExpandEnvelopeFunc(
      "POINT (1 10)", 3, "POLYGON ((-2 7, -2 13, 4 13, 4 7, -2 7))");
  testExpandEnvelopeFunc(
      "LINESTRING (1 10, 3 15)", 2, "POLYGON ((-1 8, -1 17, 5 17, 5 8, -1 8))");
  testExpandEnvelopeFunc(
      "GEOMETRYCOLLECTION (POINT (5 1), LINESTRING (3 4, 4 4))",
      1,
      "POLYGON ((2 0, 2 5, 6 5, 6 0, 2 0))");

  testExpandEnvelopeFunc("POINT EMPTY", 3, "POLYGON EMPTY");
  testExpandEnvelopeFunc("POLYGON EMPTY", 3, "POLYGON EMPTY");

  testExpandEnvelopeFunc(std::nullopt, 3, "POLYGON EMPTY");
  testExpandEnvelopeFunc("POINT EMPTY", std::nullopt, "POLYGON EMPTY");
  testExpandEnvelopeFunc(std::nullopt, std::nullopt, "POLYGON EMPTY");

  // presto-java returns empty envelopes when expand_envelope is called with
  // infinity distance, so we do the same for consistency.
  testExpandEnvelopeFunc(
      "POINT (1 1)", std::numeric_limits<double>::infinity(), "POLYGON EMPTY");

  VELOX_ASSERT_USER_THROW(
      testExpandEnvelopeFunc(
          "POINT (1 10)", -1, "POLYGON ((-2 7, -2 13, 4 13, 4 7, -2 7))"),
      "Distance must be a non-negative number");

  VELOX_ASSERT_USER_THROW(
      testExpandEnvelopeFunc(
          "POINT (1 10)",
          std::numeric_limits<double>::quiet_NaN(),
          std::nullopt),
      "Distance must be a non-NaN number");

  VELOX_ASSERT_USER_THROW(
      testExpandEnvelopeFunc(
          "POINT (1 10)",
          std::numeric_limits<double>::signaling_NaN(),
          std::nullopt),
      "Distance must be a non-NaN number");

  std::optional<std::string> wrappedEnvelopeResult = evaluateOnce<std::string>(
      "ST_AsText(expand_envelope(ST_Envelope(ST_GeometryFromText(c0)), c1))",
      std::optional<std::string>("POINT (1 10)"),
      std::optional<double>(3));
  ASSERT_TRUE(wrappedEnvelopeResult.has_value());
  ASSERT_EQ(
      wrappedEnvelopeResult.value(),
      "POLYGON ((-2 7, -2 13, 4 13, 4 7, -2 7))");
}

TEST_F(GeometryFunctionsTest, testBingTilePolygon) {
  const auto testBingTilePolygonFunc =
      [&](const std::optional<int32_t>& x,
          const std::optional<int32_t>& y,
          const std::optional<int8_t>& zoom,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "ST_AsText(bing_tile_polygon(bing_tile(c0, c1, c2)))", x, y, zoom);

        if (x.has_value() && y.has_value() && zoom.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(result.value(), expected.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  testBingTilePolygonFunc(
      0,
      0,
      0,
      "POLYGON ((-180 85.05112877980659, -180 -85.05112877980659, 180 -85.05112877980659, 180 85.05112877980659, -180 85.05112877980659))");
  testBingTilePolygonFunc(
      1,
      1,
      1,
      "POLYGON ((0 0, 0 -85.05112877980659, 180 -85.05112877980659, 180 0, 0 0))");
  testBingTilePolygonFunc(
      3,
      3,
      2,
      "POLYGON ((90 -66.51326044311185, 90 -85.05112877980659, 180 -85.05112877980659, 180 -66.51326044311185, 90 -66.51326044311185))");
  testBingTilePolygonFunc(
      7,
      7,
      3,
      "POLYGON ((135 -79.17133464081945, 135 -85.05112877980659, 180 -85.05112877980659, 180 -79.17133464081945, 135 -79.17133464081945))");
  testBingTilePolygonFunc(
      15,
      15,
      4,
      "POLYGON ((157.5 -82.67628497834906, 157.5 -85.05112877980659, 180 -85.05112877980659, 180 -82.67628497834906, 157.5 -82.67628497834906))");

  testBingTilePolygonFunc(
      31,
      31,
      5,
      "POLYGON ((168.75 -83.97925949886206, 168.75 -85.05112877980659, 180 -85.05112877980659, 180 -83.97925949886206, 168.75 -83.97925949886206))");
  testBingTilePolygonFunc(
      0,
      0,
      1,
      "POLYGON ((-180 85.05112877980659, -180 0, 0 0, 0 85.05112877980659, -180 85.05112877980659))");
  testBingTilePolygonFunc(
      1,
      1,
      2,
      "POLYGON ((-90 66.51326044311186, -90 0, 0 0, 0 66.51326044311186, -90 66.51326044311186))");
  testBingTilePolygonFunc(
      1,
      1,
      23,
      "POLYGON ((-179.99995708465576 85.05112507763845, -179.99995708465576 85.05112137546752, -179.99991416931152 85.05112137546752, -179.99991416931152 85.05112507763845, -179.99995708465576 85.05112507763845))");
}

TEST_F(GeometryFunctionsTest, testGeometryToFromGeoJson) {
  const auto testGeometryToFromGeoJsonFunc = [&](const std::optional<
                                                     std::string>& wkt,
                                                 const std::optional<
                                                     std::string>& expected =
                                                     std::nullopt) {
    std::optional<std::string> result = evaluateOnce<std::string>(
        "ST_AsText(geometry_from_geojson(geometry_as_geojson(ST_GeometryFromText(c0))))",
        wkt);

    if (wkt.has_value()) {
      ASSERT_TRUE(result.has_value());
      if (expected.has_value()) {
        ASSERT_EQ(expected.value(), result.value());
      } else {
        ASSERT_EQ(wkt.value(), result.value());
      }
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  const auto testGeometryToJsonFunc =
      [&](const std::optional<std::string>& wkt,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "geometry_as_geojson(ST_GeometryFromText(c0))", wkt);

        if (expected.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(expected.value(), result.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  const auto testGeometryFromJsonFunc =
      [&](const std::optional<std::string>& json,
          const std::optional<std::string>& expected) {
        std::optional<std::string> result = evaluateOnce<std::string>(
            "ST_AsText(geometry_from_geojson(c0))", json);

        if (expected.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_EQ(expected.value(), result.value());
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  // empty atomic (non-multi) geometries should return null
  testGeometryToJsonFunc("POINT EMPTY", std::nullopt);
  testGeometryToJsonFunc("POLYGON EMPTY", std::nullopt);
  testGeometryToJsonFunc("LINESTRING EMPTY", std::nullopt);

  testGeometryToJsonFunc(std::nullopt, std::nullopt);

  // empty multi geometries should return empty
  testGeometryToFromGeoJsonFunc("MULTIPOINT EMPTY");
  testGeometryToFromGeoJsonFunc("MULTIPOLYGON EMPTY");
  testGeometryToFromGeoJsonFunc("MULTILINESTRING EMPTY");
  testGeometryToFromGeoJsonFunc("GEOMETRYCOLLECTION EMPTY");

  // valid nonempty geometries should return as is.
  testGeometryToFromGeoJsonFunc("POINT (1 2)");
  testGeometryToFromGeoJsonFunc("MULTIPOINT (1 2, 3 4)");
  testGeometryToFromGeoJsonFunc("LINESTRING (0 0, 1 2, 3 4)");
  testGeometryToFromGeoJsonFunc("MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))");
  testGeometryToFromGeoJsonFunc("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))");
  testGeometryToFromGeoJsonFunc(
      "POLYGON ((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))");
  testGeometryToFromGeoJsonFunc(
      "MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1, 1 1)), ((2 4, 2 6, 6 6, 6 4, 2 4)))");
  testGeometryToFromGeoJsonFunc(
      "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (0 0, 1 2, 3 4), POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0)))");

  // Nested geometry collections
  testGeometryToFromGeoJsonFunc(
      "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (0 0, 1 2, 3 4), POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))))");
  testGeometryToFromGeoJsonFunc(
      "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (0 0, 1 2, 3 4), POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0)))))");
  testGeometryToFromGeoJsonFunc(
      "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION EMPTY, LINESTRING (0 0, 1 2, 3 4), POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0)))",
      "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (LINESTRING (0 0, 1 2, 3 4), POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))))");
  testGeometryToFromGeoJsonFunc(
      "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (GEOMETRYCOLLECTION EMPTY, LINESTRING EMPTY, POLYGON EMPTY))",
      "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (LINESTRING EMPTY, POLYGON EMPTY)))");
  testGeometryToFromGeoJsonFunc(
      "GEOMETRYCOLLECTION (LINESTRING EMPTY, MULTIPOINT EMPTY)");
  testGeometryToFromGeoJsonFunc(
      "GEOMETRYCOLLECTION (POINT EMPTY, POINT EMPTY)");

  // invalid geometries should return as is.
  testGeometryToFromGeoJsonFunc("MULTIPOINT (0 0, 0 1, 1 1, 0 1)");
  testGeometryToFromGeoJsonFunc("LINESTRING (0 0, 0 1, 0 1, 1 1, 1 0, 0 0)");
  testGeometryToFromGeoJsonFunc("LINESTRING (0 0, 1 1, 1 0, 0 1)");

  testGeometryFromJsonFunc(
      "{\"type\":\"LineString\",\"coordinates\":[]}", "LINESTRING EMPTY");
  testGeometryFromJsonFunc(
      "{\"type\":\"MultiPoint\",\"coordinates\":[]}", "MULTIPOINT EMPTY");
  testGeometryFromJsonFunc(
      "{\"type\":\"MultiPolygon\",\"coordinates\":[]}", "MULTIPOLYGON EMPTY");
  testGeometryFromJsonFunc(
      "{\"type\":\"MultiLineString\",\"coordinates\":[[[0.0,0.0],[1,10]],[[10,10],[20,30]],[[123,123],[456,789]]]}",
      "MULTILINESTRING ((0 0, 1 10), (10 10, 20 30), (123 123, 456 789))");
  testGeometryFromJsonFunc(
      "{\"type\":\"Polygon\",\"coordinates\":[]}", "POLYGON EMPTY");

  testGeometryFromJsonFunc(std::nullopt, std::nullopt);

  VELOX_ASSERT_USER_THROW(
      testGeometryFromJsonFunc(
          "{\"type\":\"MultiPoint\",\"invalidField\":[]}", std::nullopt),
      "Error parsing JSON");

  VELOX_ASSERT_USER_THROW(
      testGeometryFromJsonFunc(
          "{\"coordinates\":[[[0.0,0.0],[1,10]],[[10,10],[20,30]],[[123,123],[456,789]]]}",
          std::nullopt),
      "Error parsing JSON");
  VELOX_ASSERT_USER_THROW(
      testGeometryFromJsonFunc(
          "{\"type\":\"MultiPoint\",\"crashMe\"}", std::nullopt),
      "Error parsing JSON");
}

TEST_F(GeometryFunctionsTest, testGreatCircleDistance) {
  const auto testGreatCircleDistanceFunc =
      [&](const std::optional<double>& lat1,
          const std::optional<double>& long1,
          const std::optional<double>& lat2,
          const std::optional<double>& long2,
          const std::optional<double>& expected) {
        std::optional<double> result = evaluateOnce<double>(
            "great_circle_distance(c0, c1, c2, c3)", lat1, long1, lat2, long2);

        if (expected.has_value()) {
          ASSERT_TRUE(result.has_value());
          ASSERT_TRUE(std::abs(result.value() - expected.value()) < .0001);
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  testGreatCircleDistanceFunc(
      36.12, -86.67, 33.94, -118.40, 2886.4489734366994);
  testGreatCircleDistanceFunc(
      33.94, -118.40, 36.12, -86.67, 2886.4489734366994);
  testGreatCircleDistanceFunc(
      42.3601, -71.0589, 42.4430, -71.2290, 16.734697434573437);
  testGreatCircleDistanceFunc(36.12, -86.67, 36.12, -86.67, 0.0);

  VELOX_ASSERT_USER_THROW(
      testGreatCircleDistanceFunc(91, 20, 30, 40, std::nullopt),
      "Latitude must be in range [-90, 90] and longitude must be in range [-180, 180]. Got latitude: 91 and longitude: 20");
  VELOX_ASSERT_USER_THROW(
      testGreatCircleDistanceFunc(10, 20, 91, 40, std::nullopt),
      "Latitude must be in range [-90, 90] and longitude must be in range [-180, 180]. Got latitude: 91 and longitude: 40");
  VELOX_ASSERT_USER_THROW(
      testGreatCircleDistanceFunc(10, 181, 30, 40, std::nullopt),
      "Latitude must be in range [-90, 90] and longitude must be in range [-180, 180]. Got latitude: 10 and longitude: 181");
  VELOX_ASSERT_USER_THROW(
      testGreatCircleDistanceFunc(10, 20, 30, -181, std::nullopt),
      "Latitude must be in range [-90, 90] and longitude must be in range [-180, 180]. Got latitude: 30 and longitude: -181");
  VELOX_ASSERT_USER_THROW(
      testGreatCircleDistanceFunc(
          std::numeric_limits<double>::infinity(), 20, 30, 40, std::nullopt),
      "Latitude must be in range [-90, 90] and longitude must be in range [-180, 180]. Got latitude: inf and longitude: 20");
  VELOX_ASSERT_USER_THROW(
      testGreatCircleDistanceFunc(
          10, 20, 30, std::numeric_limits<double>::quiet_NaN(), std::nullopt),
      "Latitude must be in range [-90, 90] and longitude must be in range [-180, 180]. Got latitude: 30 and longitude: nan");
  VELOX_ASSERT_USER_THROW(
      testGreatCircleDistanceFunc(
          10,
          20,
          30,
          std::numeric_limits<double>::signaling_NaN(),
          std::nullopt),
      "Latitude must be in range [-90, 90] and longitude must be in range [-180, 180]. Got latitude: 30 and longitude: nan");
}

TEST_F(GeometryFunctionsTest, testGeometryToBingTiles) {
  const auto testGeometryToBingTilesFunc = [&](const std::optional<std::string>&
                                                   wkt,
                                               const std::optional<int32_t>
                                                   zoom,
                                               const std::optional<std::vector<
                                                   std::optional<std::string>>>&
                                                   expectedQuadKeys) {
    std::vector<std::optional<std::string>> wktVec = {wkt};
    auto inputWkt = makeNullableFlatVector<std::string>(wktVec);
    std::vector<std::optional<int32_t>> zoomVec = {zoom};
    auto inputZoom = makeNullableFlatVector<int32_t>(zoomVec);
    auto inputVec = makeRowVector({inputWkt, inputZoom});

    facebook::velox::VectorPtr output = evaluate(
        "ARRAY_SORT(TRANSFORM(geometry_to_bing_tiles(ST_GeometryFromText(c0), c1), x -> bing_tile_quadkey(x)))",
        inputVec);

    auto arrayVector =
        std::dynamic_pointer_cast<facebook::velox::ArrayVector>(output);

    if (expectedQuadKeys.has_value()) {
      ASSERT_TRUE(arrayVector != nullptr);
      auto expected = makeNullableArrayVector<std::string>({expectedQuadKeys});
      facebook::velox::test::assertEqualVectors(expected, output);
    } else {
      ASSERT_TRUE(output->isNullAt(0));
    }
  };

  const auto assertPointInCoveringFunc = [&](const std::optional<std::string>&
                                                 wkt,
                                             const std::optional<double> x,
                                             const std::optional<double> y,
                                             const std::optional<int8_t> zoom) {
    std::optional<bool> result = evaluateOnce<bool>(
        "contains(geometry_to_bing_tiles(ST_GeometryFromText(c0), c1), geometry_to_bing_tiles(ST_Point(c2, c3), c1)[1])",
        wkt,
        zoom,
        x,
        y);
    ASSERT_TRUE(result.has_value());
    ASSERT_TRUE(result.value());
  };

  queryCtx_->testingOverrideConfigUnsafe(
      {{core::QueryConfig::kDebugBingTileChildrenMaxZoomShift, "20"}});

  // Geometries at boundaries of tiles
  testGeometryToBingTilesFunc("POINT (0 0)", 1, {{"3"}});
  testGeometryToBingTilesFunc(
      fmt::format("POINT ({} 0)", BingTileType::kMinLongitude), 1, {{"2"}});
  testGeometryToBingTilesFunc(
      fmt::format("POINT ({} 0)", BingTileType::kMaxLongitude), 1, {{"3"}});
  testGeometryToBingTilesFunc(
      fmt::format("POINT (0 {})", BingTileType::kMinLatitude), 1, {{"3"}});
  testGeometryToBingTilesFunc(
      fmt::format("POINT (0 {})", BingTileType::kMaxLatitude), 1, {{"1"}});
  testGeometryToBingTilesFunc(
      fmt::format(
          "POINT ({} {})",
          BingTileType::kMinLongitude,
          BingTileType::kMinLatitude),
      1,
      {{"2"}});
  testGeometryToBingTilesFunc(
      fmt::format(
          "POINT ({} {})",
          BingTileType::kMinLongitude,
          BingTileType::kMaxLatitude),
      1,
      {{"0"}});
  testGeometryToBingTilesFunc(
      fmt::format(
          "POINT ({} {})",
          BingTileType::kMaxLongitude,
          BingTileType::kMaxLatitude),
      1,
      {{"1"}});
  testGeometryToBingTilesFunc(
      fmt::format(
          "POINT ({} {})",
          BingTileType::kMaxLongitude,
          BingTileType::kMinLatitude),
      1,
      {{"3"}});
  testGeometryToBingTilesFunc("LINESTRING (-1 0, -2 0)", 1, {{"2"}});
  testGeometryToBingTilesFunc("LINESTRING (1 0, 2 0)", 1, {{"3"}});
  testGeometryToBingTilesFunc("LINESTRING (0 -1, 0 -2)", 1, {{"3"}});
  testGeometryToBingTilesFunc("LINESTRING (0 1, 0 2)", 1, {{"1"}});
  testGeometryToBingTilesFunc(
      fmt::format(
          "LINESTRING ({} 1, {} 2)",
          BingTileType::kMinLongitude,
          BingTileType::kMinLongitude),
      1,
      {{"0"}});
  testGeometryToBingTilesFunc(
      fmt::format(
          "LINESTRING ({} -1, {} -2)",
          BingTileType::kMinLongitude,
          BingTileType::kMinLongitude),
      1,
      {{"2"}});
  testGeometryToBingTilesFunc(
      fmt::format(
          "LINESTRING ({} 1, {} 2)",
          BingTileType::kMaxLongitude,
          BingTileType::kMaxLongitude),
      1,
      {{"1"}});
  testGeometryToBingTilesFunc(
      fmt::format(
          "LINESTRING ({} -1, {} -2)",
          BingTileType::kMaxLongitude,
          BingTileType::kMaxLongitude),
      1,
      {{"3"}});

  // Make sure corners are included
  assertPointInCoveringFunc("POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))", 6, 0, 0);
  assertPointInCoveringFunc(
      "POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))", 6, 0, 10);
  assertPointInCoveringFunc(
      "POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))", 6, 10, 10);
  assertPointInCoveringFunc(
      "POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))", 6, 10, 0);

  // General geometries
  testGeometryToBingTilesFunc("POINT (60 30.12)", 0, {{""}});
  testGeometryToBingTilesFunc("POINT (60 30.12)", 10, {{"1230301230"}});
  testGeometryToBingTilesFunc("POINT (60 30.12)", 15, {{"123030123010121"}});
  testGeometryToBingTilesFunc("POINT (60 30.12)", 16, {{"1230301230101212"}});
  testGeometryToBingTilesFunc(
      "POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))",
      6,
      {{"122220", "122221", "122222", "122223", "300000", "300001"}});
  testGeometryToBingTilesFunc(
      "POLYGON ((0 0, 0 10, 10 10, 0 0))",
      6,
      {{"122220", "122221", "122222", "300000"}});
  testGeometryToBingTilesFunc(
      "POLYGON ((10 10, -10 10, -20 -15, 10 10))", 3, {{"033", "122", "211"}});
  testGeometryToBingTilesFunc(
      "POLYGON ((10 10, -10 10, -20 -15, 10 10))",
      6,
      {{"033321",
        "033323",
        "033330",
        "033331",
        "033332",
        "033333",
        "122220",
        "122221",
        "122222",
        "211101",
        "211102",
        "211103",
        "211110",
        "211111",
        "211112",
        "211120",
        "211121"}});

  testGeometryToBingTilesFunc(
      "GEOMETRYCOLLECTION (POINT (60 30.12))", 10, {{"1230301230"}});
  testGeometryToBingTilesFunc(
      "GEOMETRYCOLLECTION (POINT (60 30.12))", 15, {{"123030123010121"}});
  testGeometryToBingTilesFunc(
      "GEOMETRYCOLLECTION (POLYGON ((10 10, -10 10, -20 -15, 10 10)))",
      3,
      {{"033", "122", "211"}});
  testGeometryToBingTilesFunc(
      "GEOMETRYCOLLECTION (POINT (60 30.12), POLYGON ((10 10, -10 10, -20 -15, 10 10)))",
      3,
      {{"033", "122", "123", "211"}});

  testGeometryToBingTilesFunc(
      "POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))", 1, {{"1", "3"}});
  testGeometryToBingTilesFunc(
      "POLYGON ((0 0, 0 10, 10 10, 0 0))", 1, {{"1", "3"}});
  testGeometryToBingTilesFunc(
      "POLYGON ((10 10, 10 20, 20 20, 20 10, 10 10), (12 12, 12 14, 14 14, 14 12, 12 12))",
      1,
      {{"1"}});

  testGeometryToBingTilesFunc("POINT (0 0)", 1, {{"3"}});
  testGeometryToBingTilesFunc(
      "POLYGON ((0 0, 0 10, 10 10, 0 0))", 1, {{"1", "3"}});

  // Empty geometries
  testGeometryToBingTilesFunc(
      "POINT EMPTY", 10, common::testutil::optionalEmpty);
  testGeometryToBingTilesFunc(
      "POLYGON EMPTY", 10, common::testutil::optionalEmpty);
  testGeometryToBingTilesFunc(
      "GEOMETRYCOLLECTION EMPTY", 10, common::testutil::optionalEmpty);

  // Geometries at MIN_LONGITUDE/MAX_LATITUDE
  testGeometryToBingTilesFunc(
      "LINESTRING (-180 -79.19245, -180 -79.17133464081945)",
      8,
      {{"22200000"}});
  testGeometryToBingTilesFunc(
      fmt::format("POINT ({} 0)", BingTileType::kMinLongitude), 5, {{"20000"}});
  testGeometryToBingTilesFunc(
      fmt::format("POINT (0 {})", BingTileType::kMaxLatitude), 5, {{"10000"}});
  testGeometryToBingTilesFunc(
      fmt::format(
          "POINT ({} {})",
          BingTileType::kMinLongitude,
          BingTileType::kMaxLatitude),
      5,
      {{"00000"}});

  // Invalid inputs
  // Longitude out of range
  VELOX_ASSERT_USER_THROW(
      testGeometryToBingTilesFunc("POINT (600 30.12)", 10, std::nullopt),
      "Longitude span for the geometry must be in [-180.00, 180.00] range");
  VELOX_ASSERT_USER_THROW(
      testGeometryToBingTilesFunc(
          "POLYGON ((1000 10, -10 10, -20 -15, 1000 10))", 10, std::nullopt),
      "Longitude span for the geometry must be in [-180.00, 180.00] range");
  // Latitude out of range
  VELOX_ASSERT_USER_THROW(
      testGeometryToBingTilesFunc("POINT (60 300.12)", 10, std::nullopt),
      "Latitude span for the geometry must be in [-85.05, 85.05] range");
  VELOX_ASSERT_USER_THROW(
      testGeometryToBingTilesFunc(
          "POLYGON ((10 1000, -10 10, -20 -15, 10 1000))", 10, std::nullopt),
      "Latitude span for the geometry must be in [-85.05, 85.05] range");
  // Invalid zoom
  VELOX_ASSERT_USER_THROW(
      testGeometryToBingTilesFunc("POINT (60 30.12)", -1, std::nullopt),
      "Zoom level must be between 0 and 23");
  VELOX_ASSERT_USER_THROW(
      testGeometryToBingTilesFunc("POINT (60 30.12)", 24, std::nullopt),
      "Zoom level must be between 0 and 23");
  // Input rectangle too large
  VELOX_ASSERT_USER_THROW(
      evaluateOnce<int64_t>(
          "geometry_to_bing_tiles(ST_Envelope(ST_GeometryFromText(c0)), c1)[1]",
          std::optional<std::string>("LINESTRING (0 0, 80 80)"),
          std::optional<int32_t>(16)),
      "The zoom level is too high or the geometry is too large to compute a set of covering Bing tiles. Please use a lower zoom level, or tile only a section of the geometry.");
  ASSERT_EQ(
      112,
      evaluateOnce<int64_t>(
          "cardinality(geometry_to_bing_tiles(ST_Envelope(ST_GeometryFromText(c0)), c1))",
          std::optional<std::string>("LINESTRING (0 0, 80 80)"),
          std::optional<int32_t>(5)));

  ASSERT_EQ(
      9043,
      evaluateOnce<int64_t>(
          "cardinality(geometry_to_bing_tiles(ST_GeometryFromText(c0), c1))",
          std::optional<std::string>(largeWkt),
          std::optional<int32_t>(16)));
  ASSERT_EQ(
      19939,
      evaluateOnce<int64_t>(
          "cardinality(geometry_to_bing_tiles(ST_Envelope(ST_GeometryFromText(c0)), c1))",
          std::optional<std::string>(largeWkt),
          std::optional<int32_t>(16)));

  // Zoom level is too high
  VELOX_ASSERT_USER_THROW(
      evaluateOnce<int64_t>(
          "cardinality(geometry_to_bing_tiles(ST_GeometryFromText(c0), c1))",
          std::optional<std::string>("POLYGON ((0 0, 0 20, 20 20, 0 0))"),
          std::optional<int32_t>(20)),
      "The zoom level is too high or the geometry is too large to compute a set of covering Bing tiles. Please use a lower zoom level, or tile only a section of the geometry.");
  ASSERT_EQ(
      428788,
      evaluateOnce<int64_t>(
          "cardinality(geometry_to_bing_tiles(ST_GeometryFromText(c0), c1))",
          std::optional<std::string>("POLYGON ((0 0, 0 20, 20 20, 0 0))"),
          std::optional<int32_t>(14)));
}

TEST_F(GeometryFunctionsTest, testGeometryToDissolvedBingTiles) {
  const auto testGeometryToDissolvedBingTilesFunc = [&](const std::optional<
                                                            std::string>& wkt,
                                                        const std::optional<
                                                            int32_t> zoom,
                                                        const std::optional<
                                                            std::vector<
                                                                std::optional<
                                                                    std::
                                                                        string>>>&
                                                            expectedQuadKeys) {
    std::vector<std::optional<std::string>> wktVec = {wkt};
    auto inputWkt = makeNullableFlatVector<std::string>(wktVec);
    std::vector<std::optional<int32_t>> zoomVec = {zoom};
    auto inputZoom = makeNullableFlatVector<int32_t>(zoomVec);
    auto inputVec = makeRowVector({inputWkt, inputZoom});

    facebook::velox::VectorPtr output = evaluate(
        "ARRAY_SORT(TRANSFORM(geometry_to_dissolved_bing_tiles(ST_GeometryFromText(c0), c1), x -> bing_tile_quadkey(x)))",
        inputVec);

    auto arrayVector =
        std::dynamic_pointer_cast<facebook::velox::ArrayVector>(output);

    if (expectedQuadKeys.has_value()) {
      ASSERT_TRUE(arrayVector != nullptr);
      auto expected = makeNullableArrayVector<std::string>({expectedQuadKeys});
      facebook::velox::test::assertEqualVectors(expected, output);
    } else {
      ASSERT_TRUE(output->isNullAt(0));
    }
  };

  // Empty geometries
  testGeometryToDissolvedBingTilesFunc(
      "POINT EMPTY", 0, common::testutil::optionalEmpty);
  testGeometryToDissolvedBingTilesFunc(
      "POINT EMPTY", 10, common::testutil::optionalEmpty);
  testGeometryToDissolvedBingTilesFunc(
      "POINT EMPTY", 23, common::testutil::optionalEmpty);
  testGeometryToDissolvedBingTilesFunc(
      "POLYGON EMPTY", 10, common::testutil::optionalEmpty);
  testGeometryToDissolvedBingTilesFunc(
      "GEOMETRYCOLLECTION EMPTY", 10, common::testutil::optionalEmpty);

  // Geometries at tile borders
  testGeometryToDissolvedBingTilesFunc("POINT (0 0)", 0, {{""}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format("POINT ({} 0)", BingTileType::kMinLongitude), 0, {{""}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format("POINT ({} 0)", BingTileType::kMaxLongitude), 0, {{""}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format("POINT (0 {})", BingTileType::kMinLatitude), 0, {{""}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format("POINT (0 {})", BingTileType::kMaxLatitude), 0, {{""}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format(
          "POINT ({} {})",
          BingTileType::kMinLongitude,
          BingTileType::kMinLatitude),
      0,
      {{""}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format(
          "POINT ({} {})",
          BingTileType::kMinLongitude,
          BingTileType::kMaxLatitude),
      0,
      {{""}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format(
          "POINT ({} {})",
          BingTileType::kMaxLongitude,
          BingTileType::kMaxLatitude),
      0,
      {{""}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format(
          "POINT ({} {})",
          BingTileType::kMaxLongitude,
          BingTileType::kMinLatitude),
      0,
      {{""}});
  testGeometryToDissolvedBingTilesFunc("POINT (0 0)", 1, {{"3"}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format("POINT ({} 0)", BingTileType::kMinLongitude), 1, {{"2"}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format("POINT ({} 0)", BingTileType::kMaxLongitude), 1, {{"3"}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format("POINT (0 {})", BingTileType::kMinLatitude), 1, {{"3"}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format("POINT (0 {})", BingTileType::kMaxLatitude), 1, {{"1"}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format(
          "POINT ({} {})",
          BingTileType::kMinLongitude,
          BingTileType::kMinLatitude),
      1,
      {{"2"}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format(
          "POINT ({} {})",
          BingTileType::kMinLongitude,
          BingTileType::kMaxLatitude),
      1,
      {{"0"}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format(
          "POINT ({} {})",
          BingTileType::kMaxLongitude,
          BingTileType::kMaxLatitude),
      1,
      {{"1"}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format(
          "POINT ({} {})",
          BingTileType::kMaxLongitude,
          BingTileType::kMinLatitude),
      1,
      {{"3"}});
  testGeometryToDissolvedBingTilesFunc("LINESTRING (-1 0, -2 0)", 1, {{"2"}});
  testGeometryToDissolvedBingTilesFunc("LINESTRING (1 0, 2 0)", 1, {{"3"}});
  testGeometryToDissolvedBingTilesFunc("LINESTRING (0 -1, 0 -2)", 1, {{"3"}});
  testGeometryToDissolvedBingTilesFunc("LINESTRING (0 1, 0 2)", 1, {{"1"}});

  testGeometryToDissolvedBingTilesFunc(
      fmt::format(
          "LINESTRING ({} 1, {} 2)",
          BingTileType::kMinLongitude,
          BingTileType::kMinLongitude),
      1,
      {{"0"}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format(
          "LINESTRING ({} -1, {} -2)",
          BingTileType::kMinLongitude,
          BingTileType::kMinLongitude),
      1,
      {{"2"}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format(
          "LINESTRING ({} 1, {} 2)",
          BingTileType::kMaxLongitude,
          BingTileType::kMaxLongitude),
      1,
      {{"1"}});
  testGeometryToDissolvedBingTilesFunc(
      fmt::format(
          "LINESTRING ({} -1, {} -2)",
          BingTileType::kMaxLongitude,
          BingTileType::kMaxLongitude),
      1,
      {{"3"}});

  // General Geometries
  testGeometryToDissolvedBingTilesFunc("POINT (60 30.12)", 0, {{""}});
  testGeometryToDissolvedBingTilesFunc(
      "POINT (60 30.12)", 10, {{"1230301230"}});
  testGeometryToDissolvedBingTilesFunc(
      "POINT (60 30.12)", 15, {{"123030123010121"}});
  testGeometryToDissolvedBingTilesFunc(
      "POINT (60 30.12)", 16, {{"1230301230101212"}});
  testGeometryToDissolvedBingTilesFunc(
      "POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))",
      6,
      {{"12222", "300000", "300001"}});
  testGeometryToDissolvedBingTilesFunc(
      "POLYGON ((0 0, 0 10, 10 10, 0 0))",
      6,
      {{"122220", "122221", "122222", "300000"}});
  testGeometryToDissolvedBingTilesFunc(
      "POLYGON ((10 10, -10 10, -20 -15, 10 10))", 3, {{"033", "122", "211"}});
  testGeometryToDissolvedBingTilesFunc(
      "POLYGON ((10 10, -10 10, -20 -15, 10 10))",
      6,
      {{"033321",
        "033323",
        "03333",
        "122220",
        "122221",
        "122222",
        "211101",
        "211102",
        "211103",
        "211110",
        "211111",
        "211112",
        "211120",
        "211121"}});
  testGeometryToDissolvedBingTilesFunc(
      "GEOMETRYCOLLECTION (POINT (60 30.12))", 10, {{"1230301230"}});
  testGeometryToDissolvedBingTilesFunc(
      "GEOMETRYCOLLECTION (POINT (60 30.12))", 15, {{"123030123010121"}});
  testGeometryToDissolvedBingTilesFunc(
      "GEOMETRYCOLLECTION (POLYGON ((10 10, -10 10, -20 -15, 10 10)))",
      3,
      {{"033", "122", "211"}});
  testGeometryToDissolvedBingTilesFunc(
      "GEOMETRYCOLLECTION (POINT (60 30.12), POLYGON ((10 10, -10 10, -20 -15, 10 10)))",
      3,
      {{"033", "122", "123", "211"}});
  testGeometryToDissolvedBingTilesFunc(
      "GEOMETRYCOLLECTION (POINT (0.1 0.1), POINT(0.1 -0.1), POINT(-0.1 -0.1), POINT(-0.1 0.1))",
      3,
      {{"033", "122", "211", "300"}});

  ASSERT_EQ(
      2320,
      evaluateOnce<int64_t>(
          "cardinality(geometry_to_dissolved_bing_tiles(ST_GeometryFromText(c0), c1))",
          std::optional<std::string>("POLYGON ((0 0, 0 20, 20 20, 0 0))"),
          std::optional<int32_t>(14)));

  // Invalid inputs
  // Longitude out of range
  VELOX_ASSERT_USER_THROW(
      testGeometryToDissolvedBingTilesFunc(
          "POINT (600 30.12)", 10, std::nullopt),
      "Longitude span for the geometry must be in [-180.00, 180.00] range");
  VELOX_ASSERT_USER_THROW(
      testGeometryToDissolvedBingTilesFunc(
          "POLYGON ((1000 10, -10 10, -20 -15, 1000 10))", 10, std::nullopt),
      "Longitude span for the geometry must be in [-180.00, 180.00] range");
  // Latitude out of range
  VELOX_ASSERT_USER_THROW(
      testGeometryToDissolvedBingTilesFunc(
          "POINT (60 300.12)", 10, std::nullopt),
      "Latitude span for the geometry must be in [-85.05, 85.05] range");
  VELOX_ASSERT_USER_THROW(
      testGeometryToDissolvedBingTilesFunc(
          "POLYGON ((10 1000, -10 10, -20 -15, 10 1000))", 10, std::nullopt),
      "Latitude span for the geometry must be in [-85.05, 85.05] range");
  // Invalid zoom
  VELOX_ASSERT_USER_THROW(
      testGeometryToDissolvedBingTilesFunc(
          "POINT (60 30.12)", -1, std::nullopt),
      "Zoom level must be between 0 and 23");
  VELOX_ASSERT_USER_THROW(
      testGeometryToDissolvedBingTilesFunc(
          "POINT (60 30.12)", 24, std::nullopt),
      "Zoom level must be between 0 and 23");
}

TEST_F(GeometryFunctionsTest, testGeometryUnion) {
  const auto testGeometryUnionFunc = [&](const std::optional<std::vector<
                                             std::optional<std::string>>>& wkts,
                                         const std::optional<std::string>&
                                             expected) {
    auto arrayVec = makeNullableArrayVector<std::string>({{wkts}});
    auto input = makeRowVector(
        {arrayVec, makeNullableFlatVector<std::string>({expected})});
    std::optional<bool> result = evaluateOnce<bool>(
        "ST_Equals(GEOMETRY_UNION(transform(c0, x -> ST_GEOMETRYFROMTEXT(x))), ST_GeometryFromText(c1))",
        input);

    if (expected.has_value()) {
      ASSERT_TRUE(result.has_value());
      ASSERT_TRUE(result.value());
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  testGeometryUnionFunc({{"POINT EMPTY", "POINT (1 1)"}}, "POINT (1 1)");
  testGeometryUnionFunc({{"POINT (1 1)", "POINT (1 1)"}}, "POINT (1 1)");
  testGeometryUnionFunc({{"POINT (1 1)"}}, "POINT (1 1)");
  testGeometryUnionFunc(
      {{"POINT (1 2)", "POINT (3 4)"}}, "MULTIPOINT (1 2, 3 4)");

  testGeometryUnionFunc({{"LINESTRING (0 0, 1 1)"}}, "LINESTRING (0 0, 1 1)");

  testGeometryUnionFunc({{"POINT EMPTY", "LINESTRING EMPTY"}}, "POLYGON EMPTY");

  testGeometryUnionFunc(
      {{"POINT EMPTY", "POLYGON EMPTY", "MULTIPOINT EMPTY"}}, "POLYGON EMPTY");
  testGeometryUnionFunc(
      {{"GEOMETRYCOLLECTION ( POLYGON ((2 2, 3 1, 1 1, 2 2)), POLYGON ((3 2, 4 1, 2 1, 3 2)) )",
        "GEOMETRYCOLLECTION ( POLYGON ((4 2, 5 1, 3 1, 4 2)) )"}},
      "POLYGON ((2.5 1.5, 3 2, 3.5 1.5, 4 2, 5 1, 4 1, 3 1, 2 1, 1 1, 2 2, 2.5 1.5))");

  testGeometryUnionFunc(
      {{"POLYGON ((1 1, 3 1, 3 3, 1 3, 1 1))", "POINT (5 2)"}},
      "GEOMETRYCOLLECTION (POINT (5 2), POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1)))");

  // Point test cases
  testGeometryUnionFunc(
      {{"POINT (1 2)", "POINT (1 2)", "POINT (1 2)"}}, "POINT (1 2)");

  testGeometryUnionFunc({{"POINT EMPTY", "POINT (1 2)"}}, "POINT (1 2)");

  testGeometryUnionFunc(
      {{"POINT (1 2)", "POINT (3 4)"}}, "MULTIPOINT (1 2, 3 4)");

  // Linestring test cases
  testGeometryUnionFunc(
      {{"LINESTRING (1 1, 2 2)",
        "LINESTRING (1 1, 2 2)",
        "LINESTRING (1 1, 2 2)"}},
      "LINESTRING (1 1, 2 2)");

  testGeometryUnionFunc(
      {{"LINESTRING EMPTY", "LINESTRING (1 1, 2 2)"}}, "LINESTRING (1 1, 2 2)");

  testGeometryUnionFunc(
      {{"LINESTRING (1 1, 2 2, 3 3)", "LINESTRING (2 2, 3 3, 4 4)"}},
      "LINESTRING (1 1, 2 2, 3 3, 4 4)");

  testGeometryUnionFunc(
      {{"LINESTRING (1 1, 2 2, 3 3)", "LINESTRING (1 2, 2 3, 3 4)"}},
      "MULTILINESTRING ((1 1, 2 2, 3 3), (1 2, 2 3, 3 4))");

  testGeometryUnionFunc(
      {{"LINESTRING (1 1, 3 3)",
        "LINESTRING (3 1, 2 2)",
        "LINESTRING (2 2, 3 3)",
        "LINESTRING (2 2, 1 3)"}},
      "MULTILINESTRING ((1 1, 2 2, 3 3), (3 1, 1 3))");

  // Polygon test cases
  testGeometryUnionFunc(
      {{"POLYGON ((2 2, 1 1, 3 1, 2 2))",
        "POLYGON ((2 2, 1 1, 3 1, 2 2))",
        "POLYGON ((2 2, 1 1, 3 1, 2 2))"}},
      "POLYGON ((2 2, 1 1, 3 1, 2 2))");

  testGeometryUnionFunc(
      {{"POLYGON EMPTY)", "POLYGON ((2 2, 1 1, 3 1, 2 2))"}},
      "POLYGON ((2 2, 1 1, 3 1, 2 2))");

  testGeometryUnionFunc(
      {{"POLYGON ((2 2, 3 1, 1 1, 2 2))",
        "POLYGON ((3 2, 4 1, 2 1, 3 2))",
        "POLYGON ((4 2, 5 1, 3 1, 4 2))"}},
      "POLYGON ((1 1, 2 1, 3 1, 4 1, 5 1, 4 2, 3.5 1.5, 3 2, 2.5 1.5, 2 2, 1 1))");

  testGeometryUnionFunc(
      {{"POLYGON ((2 2, 3 1, 1 1, 2 2))", "POLYGON ((4 2, 5 1, 3 1, 4 2))"}},
      "MULTIPOLYGON (((1 1, 3 1, 2 2, 1 1)), ((3 1, 5 1, 4 2, 3 1)))");

  testGeometryUnionFunc(
      {{"POLYGON ((2 2, 3 1, 1 1, 2 2))", "POLYGON ((5 2, 6 1, 4 1, 5 2))"}},
      "MULTIPOLYGON (((1 1, 3 1, 2 2, 1 1)), ((4 1, 6 1, 5 2, 4 1)))");

  testGeometryUnionFunc(
      {{"POLYGON ((1 1, 6 1, 6 6, 1 6, 1 1), (3 3, 4 3, 4 4, 3 4, 3 3))",
        "POLYGON ((3 3, 4 3, 4 4, 3 4, 3 3))"}},
      "POLYGON ((1 1, 6 1, 6 6, 1 6, 1 1))");

  testGeometryUnionFunc(
      {{"POLYGON ((1 1, 6 1, 6 6, 1 6, 1 1), (3 3, 4 3, 4 4, 3 4, 3 3))",
        "POLYGON ((2 2, 5 2, 5 5, 2 5, 2 2))"}},
      "POLYGON ((1 1, 6 1, 6 6, 1 6, 1 1))");

  testGeometryUnionFunc(
      {{"POLYGON ((1 1, 6 1, 6 6, 1 6, 1 1), (3 3, 4 3, 4 4, 3 4, 3 3))",
        "POLYGON ((3.25 3.25, 3.75 3.25, 3.75 3.75, 3.25 3.75, 3.25 3.25))"}},
      "MULTIPOLYGON (((1 1, 6 1, 6 6, 1 6, 1 1), (3 3, 4 3, 4 4, 3 4, 3 3)), ((3.25 3.25, 3.75 3.25, 3.75 3.75, 3.25 3.75, 3.25 3.25)))");

  testGeometryUnionFunc(
      {{"POLYGON ((1 1, 6 1, 6 6, 1 6, 1 1), (3 3, 4 3, 4 4, 3 4, 3 3))",
        "POLYGON ((3 3, 3 3.5, 3.5 3.5, 3.5 3, 3 3))",
        "POLYGON ((3.5 3.5, 3.5 4, 4 4, 4 3.5, 3.5 3.5))",
        "POLYGON ((3 3.5, 3 4, 3.5 4, 3.5 3.5, 3 3.5))",
        "POLYGON ((3.5 3, 3.5 3.5, 4 3.5, 4 3, 3.5 3))"}},
      "POLYGON ((1 1, 6 1, 6 6, 1 6, 1 1))");

  testGeometryUnionFunc(
      {{"POLYGON ((1 3, 1 4, 6 4, 6 3, 1 3))",
        "POLYGON ((3 1, 4 1, 4 6, 3 6, 3 1))"}},
      "POLYGON ((3 1, 4 1, 4 3, 6 3, 6 4, 4 4, 4 6, 3 6, 3 4, 1 4, 1 3, 3 3, 3 1))");

  testGeometryUnionFunc(
      {{"POLYGON ((1 3, 1 4, 3 4, 3 3, 1 3))",
        "POLYGON ((3 3, 3 4, 4 4, 4 3, 3 3))",
        "POLYGON ((4 3, 4 4, 6 4, 6 3, 4 3))",
        "POLYGON ((3 1, 4 1, 4 3, 3 3, 3 1))",
        "POLYGON ((3 4, 3 6, 4 6, 4 4, 3 4))"}},
      "POLYGON ((3 1, 4 1, 4 3, 6 3, 6 4, 4 4, 4 6, 3 6, 3 4, 1 4, 1 3, 3 3, 3 1))");

  testGeometryUnionFunc(
      {{"POLYGON ((1 1, 3 1, 3 3, 1 3, 1 1))", "POINT (3 2)"}},
      "POLYGON ((1 1, 3 1, 3 2, 3 3, 1 3, 1 1))");

  // Multipoint test cases
  testGeometryUnionFunc(
      {{"MULTIPOINT ((1 2), (2 4), (3 6), (4 8))",
        "MULTIPOINT ((1 2), (2 4), (3 6), (4 8))",
        "MULTIPOINT ((1 2), (2 4), (3 6), (4 8))"}},
      "MULTIPOINT ((1 2), (2 4), (3 6), (4 8))");

  testGeometryUnionFunc(
      {{"MULTIPOINT EMPTY", "MULTIPOINT ((1 2), (2 4), (3 6), (4 8))"}},
      "MULTIPOINT ((1 2), (2 4), (3 6), (4 8))");

  testGeometryUnionFunc(
      {{"MULTIPOINT ((1 2), (2 4))", "MULTIPOINT ((3 6), (4 8))"}},
      "MULTIPOINT ((1 2), (2 4), (3 6), (4 8))");

  testGeometryUnionFunc(
      {{"MULTIPOINT ((1 2), (2 4))",
        "MULTIPOINT ((2 4), (3 6))",
        "MULTIPOINT ((3 6), (4 8))"}},
      "MULTIPOINT ((1 2), (2 4), (3 6), (4 8))");

  // Multilinestring test cases
  testGeometryUnionFunc(
      {{"MULTILINESTRING ((1 5, 4 1), (2 5, 5 1))",
        "MULTILINESTRING ((1 5, 4 1), (2 5, 5 1))",
        "MULTILINESTRING ((1 5, 4 1), (2 5, 5 1))"}},
      "MULTILINESTRING ((1 5, 4 1), (2 5, 5 1))");

  testGeometryUnionFunc(
      {{"MULTILINESTRING EMPTY", "MULTILINESTRING ((1 5, 4 1), (2 5, 5 1))"}},
      "MULTILINESTRING ((1 5, 4 1), (2 5, 5 1))");

  testGeometryUnionFunc(
      {{"MULTILINESTRING ((1 5, 4 1), (3 5, 6 1))",
        "MULTILINESTRING ((2 5, 5 1), (4 5, 7 1))"}},
      "MULTILINESTRING ((1 5, 4 1), (2 5, 5 1), (3 5, 6 1), (4 5, 7 1))");

  testGeometryUnionFunc(
      {{"MULTILINESTRING ((1 5, 4 1), (3 5, 6 1))",
        "MULTILINESTRING ((2 5, 5 1), (4 5, 7 1))",
        "LINESTRING (1 3, 8 3)"}},
      "MULTILINESTRING ((1 5, 4 1), (2 5, 5 1), (3 5, 6 1), (4 5, 7 1), (1 3, 8 3))");

  // Multipolygon test cases
  testGeometryUnionFunc(
      {{"MULTIPOLYGON(((4 2, 5 1, 3 1, 4 2)), ((14 12, 15 11, 13 11, 14 12)))",
        "MULTIPOLYGON(((4 2, 5 1, 3 1, 4 2)), ((14 12, 15 11, 13 11, 14 12)))"}},
      "MULTIPOLYGON (((4 2, 3 1, 5 1, 4 2)), ((14 12, 13 11, 15 11, 14 12)))");

  testGeometryUnionFunc(
      {{"MULTIPOLYGON EMPTY",
        "MULTIPOLYGON (((4 2, 5 1, 3 1, 4 2)), ((14 12, 15 11, 13 11, 14 12)))"}},
      "MULTIPOLYGON (((4 2, 3 1, 5 1, 4 2)), ((14 12, 13 11, 15 11, 14 12)))");

  testGeometryUnionFunc(
      {{"MULTIPOLYGON ((( 0 0, 0 2, 2 2, 2 0, 0 0 )), (( 0 3, 0 5, 2 5, 2 3, 0 3 )))",
        "MULTIPOLYGON ((( 3 0, 3 2, 5 2, 5 0, 3 0 )), (( 3 3, 3 5, 5 5, 5 3, 3 3 )))"}},
      "MULTIPOLYGON (((0 0, 2 0, 2 2, 0 2, 0 0)), ((3 0, 5 0, 5 2, 3 2, 3 0)), ((0 3, 2 3, 2 5, 0 5, 0 3)), ((3 3, 5 3, 5 5, 3 5, 3 3)))");

  testGeometryUnionFunc(
      {{"MULTIPOLYGON (((2 2, 3 1, 1 1, 2 2)), ((3 2, 4 1, 2 1, 3 2)))",
        "MULTIPOLYGON(((4 2, 5 1, 3 1, 4 2)))"}},
      "POLYGON ((1 1, 2 1, 3 1, 4 1, 5 1, 4 2, 3.5 1.5, 3 2, 2.5 1.5, 2 2, 1 1))");

  testGeometryUnionFunc(
      {{"MULTIPOLYGON (((1 3, 1 4, 3 4, 3 3, 1 3)), ((3 3, 3 4, 4 4, 4 3, 3 3)), ((4 3, 4 4, 6 4, 6 3, 4 3)))",
        "MULTIPOLYGON (((3 1, 4 1, 4 3, 3 3, 3 1)), ((3 4, 3 6, 4 6, 4 4, 3 4)))"}},
      "POLYGON ((3 1, 4 1, 4 3, 6 3, 6 4, 4 4, 4 6, 3 6, 3 4, 1 4, 1 3, 3 3, 3 1))");

  // GeometryCollection test cases
  testGeometryUnionFunc(
      {{"MULTIPOLYGON (((0 0, 2 0, 2 2, 0 2, 0 0)), ((3 0, 5 0, 5 2, 3 2, 3 0)))",
        "GEOMETRYCOLLECTION ( POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0)), POLYGON ((3 0, 5 0, 5 2, 3 2, 3 0)))",
        "GEOMETRYCOLLECTION ( POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0)), POLYGON ((3 0, 5 0, 5 2, 3 2, 3 0)))"}},
      "MULTIPOLYGON (((0 0, 2 0, 2 2, 0 2, 0 0)), ((3 0, 5 0, 5 2, 3 2, 3 0)))");

  testGeometryUnionFunc(
      {{"GEOMETRYCOLLECTION EMPTY", "GEOMETRYCOLLECTION EMPTY"}},
      "GEOMETRYCOLLECTION EMPTY");

  testGeometryUnionFunc(
      {{"GEOMETRYCOLLECTION EMPTY",
        "GEOMETRYCOLLECTION ( POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0)), POLYGON ((3 0, 5 0, 5 2, 3 2, 3 0)))"}},
      "MULTIPOLYGON (((0 0, 2 0, 2 2, 0 2, 0 0)), ((3 0, 5 0, 5 2, 3 2, 3 0)))");

  testGeometryUnionFunc(
      {{"GEOMETRYCOLLECTION ( POLYGON ((2 2, 3 1, 1 1, 2 2)), POLYGON ((3 2, 4 1, 2 1, 3 2)) )",
        "GEOMETRYCOLLECTION ( POLYGON ((4 2, 5 1, 3 1, 4 2)) )"}},
      "POLYGON ((1 1, 2 1, 3 1, 4 1, 5 1, 4 2, 3.5 1.5, 3 2, 2.5 1.5, 2 2, 1 1))");

  testGeometryUnionFunc(
      {{"GEOMETRYCOLLECTION ( POLYGON (( 0 0, 0 2, 2 2, 2 0, 0 0 )), POLYGON (( 0 3, 0 5, 2 5, 2 3, 0 3 )) )",
        "GEOMETRYCOLLECTION ( POLYGON (( 3 0, 3 2, 5 2, 5 0, 3 0 )), POLYGON (( 3 3, 3 5, 5 5, 5 3, 3 3 )) )"}},
      "MULTIPOLYGON (((0 0, 2 0, 2 2, 0 2, 0 0)), ((3 0, 5 0, 5 2, 3 2, 3 0)), ((0 3, 2 3, 2 5, 0 5, 0 3)), ((3 3, 5 3, 5 5, 3 5, 3 3)))");

  testGeometryUnionFunc(
      {{"POLYGON ((1 1, 3 1, 3 3, 1 3, 1 1))", "LINESTRING (0 2, 5 2)"}},
      "GEOMETRYCOLLECTION (MULTILINESTRING ((0 2, 1 2), (3 2, 5 2)), POLYGON ((1 1, 3 1, 3 2, 3 3, 1 3, 1 2, 1 1)))");

  testGeometryUnionFunc(
      {{"POLYGON ((1 1, 3 1, 3 3, 1 3, 1 1))", "LINESTRING (0 5, 5 5)"}},
      "GEOMETRYCOLLECTION (LINESTRING (0 5, 5 5), POLYGON ((1 1, 3 1, 3 3, 1 3, 1 1)))");

  testGeometryUnionFunc(
      {{"POLYGON ((1 1, 3 1, 3 3, 1 3, 1 1))", "POINT (5 2)"}},
      "GEOMETRYCOLLECTION (POINT (5 2), POLYGON ((1 1, 3 1, 3 3, 1 3, 1 1)))");

  testGeometryUnionFunc({{"POINT EMPTY"}}, "POLYGON EMPTY");
  testGeometryUnionFunc({{"MULTIPOINT EMPTY"}}, "POLYGON EMPTY");
  testGeometryUnionFunc({{"POLYGON EMPTY"}}, "POLYGON EMPTY");
  testGeometryUnionFunc({{"MULTIPOLYGON EMPTY"}}, "POLYGON EMPTY");
  testGeometryUnionFunc({{"LINESTRING EMPTY"}}, "POLYGON EMPTY");
  testGeometryUnionFunc({{"MULTILINESTRING EMPTY"}}, "POLYGON EMPTY");
  testGeometryUnionFunc({{"GEOMETRYCOLLECTION EMPTY"}}, "POLYGON EMPTY");
  testGeometryUnionFunc(
      {{"GEOMETRYCOLLECTION EMPTY",
        "MULTILINESTRING EMPTY",
        "LINESTRING EMPTY",
        "MULTIPOLYGON EMPTY",
        "POLYGON EMPTY",
        "MULTIPOINT EMPTY",
        "POINT EMPTY"}},
      "POLYGON EMPTY");

  // Empty array should return null
  testGeometryUnionFunc(common::testutil::optionalEmpty, std::nullopt);

  // Null elements in input array should be ignored
  testGeometryUnionFunc({{std::nullopt, "POINT (1 2)"}}, "POINT (1 2)");
}

TEST_F(GeometryFunctionsTest, testStLineFromText) {
  const auto testStLineFromTextFunc =
      [&](const std::optional<std::string>& wkt) {
        auto res =
            evaluateOnce<std::string>("ST_AsText(ST_LineFromText(c0))", wkt);

        if (wkt.has_value()) {
          ASSERT_TRUE(res.has_value());
          ASSERT_EQ(wkt.value(), res.value());
        } else {
          ASSERT_FALSE(res.has_value());
        }
      };

  testStLineFromTextFunc("LINESTRING EMPTY");
  testStLineFromTextFunc("LINESTRING (1 1, 2 2, 1 3)");
  VELOX_ASSERT_USER_THROW(
      testStLineFromTextFunc("xyz"),
      "Failed to parse WKT: ParseException: Unknown type: 'XYZ'");
  VELOX_ASSERT_USER_THROW(
      testStLineFromTextFunc("MULTILINESTRING EMPTY"),
      "ST_LineFromText only applies to LineString. Input type is: MultiLineString");
  VELOX_ASSERT_USER_THROW(
      testStLineFromTextFunc("POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1))"),
      "ST_LineFromText only applies to LineString. Input type is: Polygon");
  VELOX_ASSERT_USER_THROW(
      testStLineFromTextFunc("LINESTRING (0 0)"),
      "Failed to parse WKT: IllegalArgumentException: point array must contain 0 or >1 elements");
  VELOX_ASSERT_USER_THROW(
      testStLineFromTextFunc("LINESTRING (0 0, 1)"),
      "Failed to parse WKT: ParseException: Expected number but encountered ')'");
  testStLineFromTextFunc(std::nullopt);
}

TEST_F(GeometryFunctionsTest, testStLineString) {
  const auto testStLineStringFunc = [&](const std::optional<std::vector<
                                            std::optional<std::string>>>& wkts,
                                        const std::optional<std::string>&
                                            expected) {
    auto arrayVec = makeNullableArrayVector<std::string>({{wkts}});
    auto input = makeRowVector({arrayVec});
    std::optional<std::string> result = evaluateOnce<std::string>(
        "St_AsText(ST_LineString(transform(c0, x -> ST_GEOMETRYFROMTEXT(x))))",
        input);

    if (expected.has_value()) {
      ASSERT_TRUE(result.has_value());
      ASSERT_EQ(expected.value(), result.value());
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  // Happy cases
  testStLineStringFunc(
      {{"POINT (1 2)", "POINT (3 4)"}}, "LINESTRING (1 2, 3 4)");
  testStLineStringFunc(
      {{"POINT (1 2)", "POINT (3 4)", "POINT (5 6)"}},
      "LINESTRING (1 2, 3 4, 5 6)");
  testStLineStringFunc(
      {{"POINT (1 2)", "POINT (3 4)", "POINT (5 6)", "POINT (7 8)"}},
      "LINESTRING (1 2, 3 4, 5 6, 7 8)");

  // < 2 Points returns empty
  testStLineStringFunc({{"POINT (1 2)"}}, "LINESTRING EMPTY");
  testStLineStringFunc(common::testutil::optionalEmpty, "LINESTRING EMPTY");
  // Duplicate consecutive points throws exception
  VELOX_ASSERT_USER_THROW(
      testStLineStringFunc({{"POINT (1 2)", "POINT (1 2)"}}, std::nullopt),
      "Repeated point sequence in ST_LineString: point 1,2 at index 1.");
  // Only points can be passed
  VELOX_ASSERT_USER_THROW(
      testStLineStringFunc(
          {{"POLYGON ((1 2, 3 4, 5 6, 1 2))", "POINT (1 2)"}}, std::nullopt),
      "Non-point geometry in ST_LineString input at index 0.");
  // Empty points are invalid
  VELOX_ASSERT_USER_THROW(
      testStLineStringFunc({{"POINT (1 2)", "POINT EMPTY"}}, std::nullopt),
      "Empty point in ST_LineString input at index 1.");
  VELOX_ASSERT_USER_THROW(
      testStLineStringFunc(
          {{"POINT (1 2)", "POINT EMPTY", "POINT (1 2)"}}, std::nullopt),
      "Empty point in ST_LineString input at index 1.");
  VELOX_ASSERT_USER_THROW(
      testStLineStringFunc(
          {{"POINT (1 2)", "POINT EMPTY", "POINT (1 2)", "POINT EMPTY"}},
          std::nullopt),
      "Empty point in ST_LineString input at index 1.");
  VELOX_ASSERT_USER_THROW(
      testStLineStringFunc({{"POINT EMPTY"}}, std::nullopt),
      "Empty point in ST_LineString input at index 0.");

  // Null elements in array are invalid
  VELOX_ASSERT_USER_THROW(
      testStLineStringFunc({{"POINT (1 2)", std::nullopt}}, std::nullopt),
      "Invalid input to ST_LineString: input array contains null at index 1.");
  VELOX_ASSERT_USER_THROW(
      testStLineStringFunc({{std::nullopt, "POINT (1 2)"}}, std::nullopt),
      "Invalid input to ST_LineString: input array contains null at index 0.");
  VELOX_ASSERT_USER_THROW(
      testStLineStringFunc(
          std::vector<std::optional<std::string>>{std::nullopt}, std::nullopt),
      "Invalid input to ST_LineString: input array contains null at index 0.");
  VELOX_ASSERT_USER_THROW(
      testStLineStringFunc(
          std::vector<std::optional<std::string>>{std::nullopt, std::nullopt},
          std::nullopt),
      "Invalid input to ST_LineString: input array contains null at index 0.");
}

TEST_F(GeometryFunctionsTest, testStMultiPoint) {
  const auto testStMultiPointFunc = [&](const std::optional<std::vector<
                                            std::optional<std::string>>>& wkts,
                                        const std::optional<std::string>&
                                            expected) {
    auto arrayVec = makeNullableArrayVector<std::string>({{wkts}});
    auto input = makeRowVector({arrayVec});
    std::optional<std::string> result = evaluateOnce<std::string>(
        "St_AsText(ST_MultiPoint(transform(c0, x -> ST_GEOMETRYFROMTEXT(x))))",
        input);

    if (expected.has_value()) {
      ASSERT_TRUE(result.has_value());
      ASSERT_EQ(expected.value(), result.value());
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  // Happy cases
  testStMultiPointFunc(
      {{"POINT (1 2)", "POINT (3 4)"}}, "MULTIPOINT (1 2, 3 4)");
  testStMultiPointFunc(
      {{"POINT (1 2)", "POINT (3 4)", "POINT (5 6)"}},
      "MULTIPOINT (1 2, 3 4, 5 6)");
  testStMultiPointFunc(
      {{"POINT (1 2)", "POINT (3 4)", "POINT (5 6)", "POINT (7 8)"}},
      "MULTIPOINT (1 2, 3 4, 5 6, 7 8)");

  // Duplicate points work
  testStMultiPointFunc(
      {{"POINT (1 2)", "POINT (1 2)"}}, "MULTIPOINT (1 2, 1 2)");
  testStMultiPointFunc(
      {{"POINT (1 2)", "POINT (3 4)", "POINT (1 2)"}},
      "MULTIPOINT (1 2, 3 4, 1 2)");

  // Single point
  testStMultiPointFunc({{"POINT (1 2)"}}, "MULTIPOINT (1 2)");

  // Empty array
  testStMultiPointFunc(common::testutil::optionalEmpty, std::nullopt);

  // Only points can be passed
  VELOX_ASSERT_USER_THROW(
      testStMultiPointFunc(
          {{"POINT (7 8)", "LINESTRING (1 2, 3 4)"}}, std::nullopt),
      "Non-point geometry in ST_MultiPoint input at index 1.");

  VELOX_ASSERT_USER_THROW(
      testStMultiPointFunc(
          {{"POINT (7 8)", "MULTIPOINT ((1 2), (3 4))"}}, std::nullopt),
      "Non-point geometry in ST_MultiPoint input at index 1.");

  // Null elements in array are invalid
  VELOX_ASSERT_USER_THROW(
      testStMultiPointFunc({{"POINT (1 2)", std::nullopt}}, std::nullopt),
      "Invalid input to ST_MultiPoint: input array contains null at index 1.");
  VELOX_ASSERT_USER_THROW(
      testStMultiPointFunc({{std::nullopt, "POINT (1 2)"}}, std::nullopt),
      "Invalid input to ST_MultiPoint: input array contains null at index 0.");
  VELOX_ASSERT_USER_THROW(
      testStMultiPointFunc(
          std::vector<std::optional<std::string>>{std::nullopt}, std::nullopt),
      "Invalid input to ST_MultiPoint: input array contains null at index 0.");
  VELOX_ASSERT_USER_THROW(
      testStMultiPointFunc(
          std::vector<std::optional<std::string>>{std::nullopt, std::nullopt},
          std::nullopt),
      "Invalid input to ST_MultiPoint: input array contains null at index 0.");

  // Empty elements in array are invalid
  VELOX_ASSERT_USER_THROW(
      testStMultiPointFunc({{"POINT EMPTY"}}, std::nullopt),
      "Empty point in ST_MultiPoint input at index 0.");
  VELOX_ASSERT_USER_THROW(
      testStMultiPointFunc({{"POINT (1 2)", "POINT EMPTY"}}, std::nullopt),
      "Empty point in ST_MultiPoint input at index 1.");
}

TEST_F(GeometryFunctionsTest, testToFromSphericalGeography) {
  const auto testToFromSphericalGeographyFunc = [&](const std::optional<
                                                    std::string>& wkt) {
    std::optional<std::string> result = evaluateOnce<std::string>(
        "ST_AsText(to_geometry(to_spherical_geography(ST_GeometryFromText(c0))))",
        wkt);

    if (wkt.has_value()) {
      ASSERT_TRUE(result.has_value());
      ASSERT_EQ(wkt.value(), result.value());
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  // Happy cases
  testToFromSphericalGeographyFunc("POINT EMPTY");
  testToFromSphericalGeographyFunc("MULTIPOINT EMPTY");
  testToFromSphericalGeographyFunc("POLYGON EMPTY");
  testToFromSphericalGeographyFunc("MULTIPOLYGON EMPTY");
  testToFromSphericalGeographyFunc("LINESTRING EMPTY");
  testToFromSphericalGeographyFunc("MULTILINESTRING EMPTY");
  testToFromSphericalGeographyFunc("GEOMETRYCOLLECTION EMPTY");

  testToFromSphericalGeographyFunc("POINT (180 90)");
  testToFromSphericalGeographyFunc("POINT (-180 -90)");
  testToFromSphericalGeographyFunc("POINT (1 2)");
  testToFromSphericalGeographyFunc("MULTIPOINT (1 2, 3 4, 5 6, 7 8)");
  testToFromSphericalGeographyFunc("POLYGON ((1 3, 1 4, 3 4, 3 3, 1 3))");
  testToFromSphericalGeographyFunc(
      "MULTIPOLYGON (((0 0, 0 2, 2 2, 2 0, 0 0)), ((3 0, 3 2, 5 2, 5 0, 3 0)), ((0 3, 0 5, 2 5, 2 3, 0 3)), ((3 3, 3 5, 5 5, 5 3, 3 3)))");
  testToFromSphericalGeographyFunc(
      "GEOMETRYCOLLECTION (POINT (1 1), GEOMETRYCOLLECTION (LINESTRING (0 0, 1 1), GEOMETRYCOLLECTION (POLYGON ((2 2, 2 3, 3 3, 3 2, 2 2)))))");

  // Error cases
  VELOX_ASSERT_USER_THROW(
      testToFromSphericalGeographyFunc("POINT (200 200)"),
      "Latitude must be in range [-90, 90] and longitude must be in range [-180, 180]. Got latitude: 200 and longitude: 200");
  VELOX_ASSERT_USER_THROW(
      testToFromSphericalGeographyFunc(
          "GEOMETRYCOLLECTION (POINT (1 1), GEOMETRYCOLLECTION (LINESTRING (0 0, 1 1), GEOMETRYCOLLECTION (POLYGON ((2 2, 2 -300, 3 3, 3 2, 2 2)))))"),
      "Latitude must be in range [-90, 90] and longitude must be in range [-180, 180]. Got latitude: -300 and longitude: 0");
}

TEST_F(GeometryFunctionsTest, testStSphericalCentroid) {
  const auto testStSphericalCentroidFunction = [&](const std::optional<
                                                       std::string>& wkt,
                                                   const std::optional<
                                                       std::string>&
                                                       expectedWkt) {
    std::optional<std::string> result = evaluateOnce<std::string>(
        "st_astext(to_geometry(st_centroid(to_spherical_geography(ST_GeometryFromText(c0)))))",
        wkt);

    if (expectedWkt.has_value()) {
      ASSERT_TRUE(result.has_value());
      ASSERT_EQ(result.value(), expectedWkt.value());
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  // Empty geometries return null. Note that functions that return empty
  // geometries might choose any type, so we should cover all types.
  testStSphericalCentroidFunction("POINT EMPTY", std::nullopt);
  testStSphericalCentroidFunction("MULTIPOINT EMPTY", std::nullopt);
  testStSphericalCentroidFunction("LINESTRING EMPTY", std::nullopt);
  testStSphericalCentroidFunction("MULTILINESTRING EMPTY", std::nullopt);
  testStSphericalCentroidFunction("POLYGON EMPTY", std::nullopt);
  testStSphericalCentroidFunction("MULTIPOLYGON EMPTY", std::nullopt);
  testStSphericalCentroidFunction("GEOMETRYCOLLECTION EMPTY", std::nullopt);

  // Single point returns same point
  testStSphericalCentroidFunction("POINT (3 5)", "POINT (3 5)");

  // Single point in multipoint returns same point
  testStSphericalCentroidFunction("MULTIPOINT (3 5)", "POINT (3 5)");

  // Two points on opposite sides of equator at same longitude
  testStSphericalCentroidFunction("MULTIPOINT (0 -45, 0 45)", "POINT (0 0)");

  // Two points on equator at opposite longitudes
  testStSphericalCentroidFunction("MULTIPOINT (45 0, -45 0)", "POINT (0 0)");

  // Two antipodal points on the equator (0, 0) and (-180, 0)
  // The result is arbitrary but GEOS calculates it as (-90 45)
  testStSphericalCentroidFunction("MULTIPOINT (0 0, -180 0)", "POINT (-90 45)");

  // Three points - the Java test expects (12.36780515862267, 0)
  // We'll check with some tolerance
  auto result = evaluateOnce<std::string>(
      "st_astext(to_geometry(st_centroid(to_spherical_geography(ST_GeometryFromText(c0)))))",
      std::optional<std::string>("MULTIPOINT (0 -45, 0 45, 30 0)"));
  ASSERT_TRUE(result.has_value());

  // Parse the result to check longitude is approximately 12.3678
  std::string resultStr = result.value();
  ASSERT_TRUE(resultStr.find("POINT (12.3678") != std::string::npos);

  // Four symmetric points should give centroid at (0, 0)
  testStSphericalCentroidFunction(
      "MULTIPOINT (0 -45, 0 45, 30 0, -30 0)", "POINT (0 0)");

  // Non-point/multipoint geometries should throw
  VELOX_ASSERT_USER_THROW(
      testStSphericalCentroidFunction("LINESTRING (0 0, 1 1)", std::nullopt),
      "ST_Centroid[SphericalGeography] only applies to Point or MultiPoint. Input type is: LineString");

  VELOX_ASSERT_USER_THROW(
      testStSphericalCentroidFunction(
          "POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", std::nullopt),
      "ST_Centroid[SphericalGeography] only applies to Point or MultiPoint. Input type is: Polygon");
}
TEST_F(GeometryFunctionsTest, testStSphericalDistance) {
  const auto testStSphericalDistanceFunc = [&](const std::optional<std::string>&
                                                   leftWkt,
                                               const std::optional<std::string>&
                                                   rightWkt,
                                               const std::optional<double>&
                                                   expected) {
    std::optional<double> result = evaluateOnce<double>(
        "st_distance(to_spherical_geography(ST_GeometryFromText(c0)), to_spherical_geography(ST_GeometryFromText(c1)))",
        leftWkt,
        rightWkt);

    if (expected.has_value()) {
      ASSERT_TRUE(aboutEquals(expected.value(), result.value()));
    } else {
      ASSERT_FALSE(result.has_value());
    }
  };

  // Happy cases
  testStSphericalDistanceFunc(
      "POINT (-86.67 36.12)", "POINT (-118.40 33.94)", 2886448.973436703);
  testStSphericalDistanceFunc(
      "POINT (-118.40 33.94)", "POINT (-86.67 36.12)", 2886448.973436703);
  testStSphericalDistanceFunc(
      "POINT (-71.0589 42.3601)",
      "POINT (-71.2290 42.4430)",
      16734.69743457461);
  testStSphericalDistanceFunc(
      "POINT (-86.67 36.12)", "POINT (-86.67 36.12)", 0.0);

  // Non-point geometries should throw
  VELOX_ASSERT_USER_THROW(
      testStSphericalDistanceFunc(
          "POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))",
          "POLYGON((0 0, 0 2, 2 2, 2 0, 0 0))",
          std::nullopt),
      "ST_Distance[SphericalGeography] only applies to Point. Input type is: Polygon");

  // Empty points should return null
  testStSphericalDistanceFunc("POINT EMPTY", "POINT (40 30)", std::nullopt);
  testStSphericalDistanceFunc("POINT (20 10)", "POINT EMPTY", std::nullopt);
  testStSphericalDistanceFunc("POINT EMPTY", "POINT EMPTY", std::nullopt);
}

TEST_F(GeometryFunctionsTest, testStSphericalLength) {
  const auto testStSphericalLengthFunction =
      [&](const std::optional<std::string>& wkt,
          const std::optional<double>& expected) {
        std::optional<double> result = evaluateOnce<double>(
            "st_length(to_spherical_geography(ST_GeometryFromText(c0)))", wkt);

        if (expected.has_value()) {
          ASSERT_TRUE(aboutEquals(expected.value(), result.value()));
        } else {
          ASSERT_FALSE(result.has_value());
        }
      };

  double length = 4350866.6362;

  // Empty linestring returns null
  testStSphericalLengthFunction("LINESTRING EMPTY", std::nullopt);

  // ST_Length is equivalent to sums of ST_DISTANCE between points in the
  // LineString
  testStSphericalLengthFunction(
      "LINESTRING (-71.05 42.36, -87.62 41.87, -122.41 37.77)", length);

  // Linestring has same length as its reverse
  testStSphericalLengthFunction(
      "LINESTRING (-122.41 37.77, -87.62 41.87, -71.05 42.36)", length);

  // Path north pole -> south pole -> north pole should be roughly the
  // circumference of the Earth
  testStSphericalLengthFunction(
      "LINESTRING (0.0 90.0, 0.0 -90.0, 0.0 90.0)", 4.003e7);

  // Empty multi-linestring returns null
  testStSphericalLengthFunction("MULTILINESTRING EMPTY", std::nullopt);

  // Multi-linestring with one path is equivalent to a single linestring
  testStSphericalLengthFunction(
      "MULTILINESTRING ((-71.05 42.36, -87.62 41.87, -122.41 37.77))", length);

  // Multi-linestring with two disjoint paths has length equal to sum of lengths
  // of lines
  testStSphericalLengthFunction(
      "MULTILINESTRING ((-71.05 42.36, -87.62 41.87, -122.41 37.77), (-73.05 42.36, -89.62 41.87, -124.41 37.77))",
      2 * length);

  // Multi-linestring with adjacent paths is equivalent to a single linestring
  testStSphericalLengthFunction(
      "MULTILINESTRING ((-71.05 42.36, -87.62 41.87), (-87.62 41.87, -122.41 37.77))",
      length);

  // Non-linestring geometries should throw
  VELOX_ASSERT_USER_THROW(
      testStSphericalLengthFunction(
          "POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", std::nullopt),
      "ST_Length[SphericalGeography] only applies to LineString or MultiLineString. Input type is: Polygon");

  // Invalid linestring should throw
  VELOX_ASSERT_USER_THROW(
      testStSphericalLengthFunction("LINESTRING (0.0)", std::nullopt),
      "Failed to parse WKT: ParseException: Expected number but encountered ')'");
}
